This guide explains how to run the connector with Docker, where to put your Starling access token, how to verify the Starling connection before any data is written into Grist, and how to prepare the Grist document.
This repository currently provides:
- the connector source code
- a
Dockerfilefor building the connector image locally - a
docker-compose.ymlfor running the connector container
At the moment, this is not set up as a published GitHub Release image pipeline. That means:
- you should build the image locally from this repository
- or push your own built image to your own container registry later
Current expected workflow:
- clone the repository
- create
.env - build with Docker Compose
- test in dry-run mode
- enable writes once the payload looks correct
Before treating the deployment as final, these decisions should be settled:
- confirm the correct Starling access token type for your account
- confirm the Grist document already exists
- confirm the Grist API key works against that document
- create the required Grist tables and columns
- run at least one dry-run sync
- confirm the imported field mapping is acceptable
- confirm whether you want scheduled sync enabled immediately or only after manual validation
- decide where
.envwill live on the host - decide whether port
8080should be exposed only locally or on your LAN - decide whether you want one Starling account only or all discovered accounts
- decide where Docker volumes will persist state
- decide whether you want to keep
DRY_RUN=truefor first deployment
Put the Starling token in your .env file.
Use these settings:
SOURCE_PROVIDER=starling
SOURCE_NAME=starling_bank
STARLING_ACCESS_TOKEN=your_real_starling_token_hereIf Starling requires one token per account, you can instead use:
STARLING_ACCESS_TOKENS=token_for_account_1,token_for_account_2,token_for_account_3Optional:
STARLING_ACCOUNT_UID=optional_specific_account_uidOr multiple explicit account filters:
STARLING_ACCOUNT_UIDS=account_uid_1,account_uid_2If STARLING_ACCOUNT_UID is blank, the connector will discover all accessible Starling accounts and import from each account's default category.
Do not hardcode the token in source code.
Do not commit .env to git.
This repository includes a helper script:
python3 scripts/print_starling_accounts.py --env-file .envIt reads:
STARLING_ACCESS_TOKENSTARLING_API_BASE_URL
from the env file and prints:
accountUiddefaultCategoryaccountTypecurrency
Use the printed accountUid value in:
STARLING_ACCOUNT_UID=that_account_uid_hereThis repository also includes a read-only transaction preview helper:
python3 scripts/preview_starling_transactions.py --env-file .env --days 7 --limit 5What it does:
- reads your Starling settings from the env file
- fetches transactions directly from Starling
- normalizes them using the same mapping logic as the connector
- prints sample transactions locally
- does not write anything to Grist
This is the safest way to verify:
- the token works
- the account selection is correct
- the amount sign looks right
- the description mapping looks reasonable
- the transaction date mapping is correct
git clone <your-repo-url>
cd Bank_API_Connector_for_Gristcp .env.example .envMinimum Starling + Grist settings:
LOG_LEVEL=INFO
SERVICE_PORT=8080
SCHEDULER_ENABLED=false
RUN_SYNC_ON_STARTUP=false
ENABLE_MANUAL_SYNC_ENDPOINT=true
GRIST_BASE_URL=http://grist:8484
GRIST_DOC_ID=your_grist_doc_id
GRIST_API_KEY=your_grist_api_key
GRIST_TRANSACTIONS_TABLE=Raw_Import_Transactions
GRIST_IMPORT_LOG_TABLE=Import_Log
SOURCE_PROVIDER=starling
SOURCE_NAME=starling_bank
SOURCE_ENABLED=true
STARLING_API_BASE_URL=https://api.starlingbank.com
STARLING_ACCESS_TOKEN=your_real_starling_token_here
STARLING_ACCOUNT_UID=
IMPORT_LOOKBACK_DAYS=30
DUPLICATE_MODE=skip_existing
DRY_RUN=true
BATCH_SIZE=100
STATE_DB_PATH=/data/state/connector.sqlite3
RETRY_COUNT=3
RETRY_BACKOFF_MS=1000
API_TIMEOUT_MS=15000If Grist is running in Docker too, the important part is that the connector container can resolve the hostname used in GRIST_BASE_URL.
Examples:
- If Grist is in the same Compose stack,
GRIST_BASE_URLmay behttp://grist:8484 - If Grist is on another machine, use its reachable LAN URL
- If Grist runs directly on the host, use a host-reachable address that the container can access
docker compose up --build -dcurl http://localhost:8080/healthExpected result:
- JSON response
"status": "ok"
This is the current supported path.
docker compose up --build -dIf you want to host it yourself:
docker build -t your-registry/grist-finance-connector:0.1.0 .
docker push your-registry/grist-finance-connector:0.1.0Then update docker-compose.yml to use image: instead of build:.
This repository does not currently include:
- a GitHub Actions image build workflow
- an automated GitHub Release
- a published container image
If you want release-based hosting later, the next step would be to add:
- CI build
- image tagging
- registry publishing
- release notes
The safest path is:
- keep
DRY_RUN=true - make sure the Grist tables already exist
- run a manual sync
- inspect logs and sync result
- only then change
DRY_RUN=false
curl -X POST http://localhost:8080/syncExpected result in dry-run:
- HTTP response should return success if Starling and Grist are reachable
- response should show
fetched_count - response should show
dry_run: true - no rows should be written into
Raw_Import_Transactions
You want all of these:
/healthreturnsstatus: okPOST /syncreturnssuccess: truefetched_countis greater than zero or at least consistent with the account/date range- container logs show a completed sync rather than auth or transport failure
- Grist row count stays unchanged while
DRY_RUN=true
docker compose logs -f connectorLook for:
- sync started
- sync completed
- fetched count
- inserted/updated/skipped counts
Use:
DRY_RUN=true
IMPORT_LOOKBACK_DAYS=3
SCHEDULER_ENABLED=false
RUN_SYNC_ON_STARTUP=falseThis keeps the first live test small and manual.
Once dry-run looks correct:
- change
DRY_RUN=false - restart the container
- trigger
POST /syncmanually once - confirm rows appear in Grist
- only then enable scheduling if desired
Restart:
docker compose up -d --buildYou need a Grist document with at least these two tables:
Raw_Import_TransactionsImport_Log
Create these columns exactly unless you also change the matching env vars:
| Column | Type | Required |
|---|---|---|
external_id |
Text | Yes |
source_name |
Text | Yes |
transaction_date |
Date | Yes |
description |
Text | Yes |
amount |
Numeric | Yes |
currency |
Text | Yes |
account_id |
Text | Yes |
external_reference |
Text | Yes |
Recommended extra columns:
| Column | Type |
|---|---|
imported_at |
DateTime |
review_status |
Choice |
notes |
Text |
category |
Choice or Ref |
Create these columns:
| Column | Type | Required |
|---|---|---|
source_name |
Text | Yes |
start_time |
DateTime | Yes |
end_time |
DateTime | Yes |
duration_seconds |
Numeric | Yes |
fetched_count |
Numeric | Yes |
inserted_count |
Numeric | Yes |
updated_count |
Numeric | Yes |
skipped_count |
Numeric | Yes |
failed_count |
Numeric | Yes |
status |
Text | Yes |
message |
Text | Yes |
Open your Grist document in the browser.
The document ID is the value in the URL after /doc/.
Example:
https://your-grist-host/o/docs/docIdHere
Use docIdHere as GRIST_DOC_ID.
Use this order:
- Create Grist tables
- Set
DRY_RUN=true - Start connector with Docker
- Check
GET /health - Run
POST /sync - Check response and logs
- Confirm no rows were written
- Set
DRY_RUN=false - Run
POST /syncagain - Confirm rows appear in
Raw_Import_Transactions - Confirm a row appears in
Import_Log - Enable scheduler only after this works
After validation:
SCHEDULER_ENABLED=true
SOURCE_SCHEDULE=0 * * * *
DRY_RUN=false
RUN_SYNC_ON_STARTUP=falseThis gives you:
- hourly sync
- persistent state in Docker volume
- manual health and sync endpoints
- safe restart behaviour
- validate your actual Starling token against the live API in dry-run mode
- confirm the Grist table names and column names exactly match config
- confirm the amount sign is correct for your Starling transaction direction expectations
- confirm duplicate handling mode is the one you want
- decide whether the HTTP port should remain exposed publicly or only locally
- decide whether to add auth in front of
/syncif the service is reachable outside your private network
- no published release image yet
- no automated GitHub release workflow yet
- no built-in Grist table creation automation yet
- current implementation writes transactions and import logs, not full account-table sync
- current multi-source support is still one source per service instance