Copy a week's worth of time entries from Intervals to FreshBooks. Use when asked to sync time entries between Intervals and FreshBooks.
Copy weekly time entries from Intervals to FreshBooks.
Reading from Intervals: Browser automation via MCP chrome-devtools
Writing to FreshBooks: API via scripts/freshbooks-api.sh
--remote-debugging-port=9222https://bhi.intervalsonline.com/time/~/.config/freshbooks/credentials.jsonlist_pages to find Intervals browser tabintervalsonline.com/time/)select_pagescripts/read-intervals.js using evaluate_scriptThe script reads from the summary table which has this structure:
td.col-timesheet-clientproject contains "Client\nProject"Output format:
{
success: true,
week: "January 05, 2026",
entries: [
{ client: "Technomic", project: "Ignite App...", billable: true,
hours: { mon: 7.5, tue: 4.5, wed: 4.5, thu: 0, fri: 4.5, sat: 0, sun: 0 },
totalHours: 21 }
],
grandTotal: 47.75
}
For each Intervals entry, determine the FreshBooks destination:
references/project-mappings.md with new mappingsMapping examples:
| Intervals Client | Intervals Project | FB Client | FB Project |
|---|---|---|---|
| Technomic | Ignite App... | EXSquared | Technomic |
| EWG - Neuron | Feature Enhancement | EXSquared | EWG |
| EX Squared Services | Meeting | EXSquared | (none) |
| EX Squared Services | Biz Dev / Sales | EXSquared | (none) |
Use scripts/freshbooks-api.sh to create time entries:
# List available projects and clients
./scripts/freshbooks-api.sh projects
./scripts/freshbooks-api.sh clients
# Create a time entry
./scripts/freshbooks-api.sh create-time-entry \
--project "<name>" | --client "<name>" \
--date <YYYY-MM-DD> \
--hours <hours> \
[--note "<note>"]
For each Intervals entry with hours on a given day:
create-time-entry for each day with hours > 0Examples:
# With project (client defaults to EXSquared)
./scripts/freshbooks-api.sh create-time-entry \
--project "Technomic" \
--date "2026-01-06" \
--hours 7.5 \
--note "Development"
# Client only - no project (e.g., internal meetings)
./scripts/freshbooks-api.sh create-time-entry \
--date "2026-01-06" \
--hours 1.0 \
--note "Meeting"
# Different client (rare)
./scripts/freshbooks-api.sh create-time-entry \
--client "Rocksauce Studios" \
--date "2026-01-06" \
--hours 1.0
After creating FreshBooks entries, persist them locally for comparison with Intervals data.
Use the API to list entries for the synced dates:
./scripts/freshbooks-api.sh list-time-entries --from "YYYY-MM-DD" --to "YYYY-MM-DD"
Also fetch project list to resolve project IDs → names:
./scripts/freshbooks-api.sh projects
Insert each entry into the freshbooks_time_entries table:
sqlite3 "$OBSIDIAN_VAULT_PATH/.claude/time-entries.db" <<'SQL'
CREATE TABLE IF NOT EXISTS freshbooks_time_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL,
project TEXT NOT NULL,
hours REAL NOT NULL,
description TEXT,
freshbooks_entry_id TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_fb_entries_unique
ON freshbooks_time_entries(date, project, description);
INSERT OR REPLACE INTO freshbooks_time_entries (date, project, hours, description, freshbooks_entry_id)
VALUES ('YYYY-MM-DD', 'Project Name', 2.0, 'Description', 'fb_entry_id');
SQL
project_id: use the resolved project name (e.g., "Technomic", "EWG")project_id (client-only): use "EXSquared" as project, note as descriptionFor each date with entries, append a ### FreshBooks section to the daily note:
------
### FreshBooks
| Project | Hours | Description |
|---------|------:|-------------|
| Technomic | 2.0 | Development |
| **Total** | **8.0** | |
### FreshBooks already exists in the note, replace itList created entries:
./scripts/freshbooks-api.sh list-time-entries --from "2026-01-05" --to "2026-01-11"
Display summary of entries created
Open or refresh FreshBooks in the browser for visual review:
list_pages to find FreshBooks tabselect_page then navigate_page with type: "reload"new_page with FreshBooks week URLURL format: https://my.freshbooks.com/#/time-tracking/week?week=YYYY-MM-DD
(where YYYY-MM-DD is the Monday of the week)
read-intervals.jsReads the Intervals weekly summary table. No configuration needed.
Run via evaluate_script - returns structured entry data.
freshbooks-api.shShell script for FreshBooks API operations. Supports 1Password op:// references in credentials.
Commands:
projects - List all FreshBooks projectsclients - List all FreshBooks clientsproject-id <name> - Get project ID by nameclient-id <name> - Get client ID by namecreate-time-entry - Create a time entry
--client, -c <name> - FreshBooks client (default: EXSquared)--project, -p <name> - FreshBooks project (optional)--date, -d <YYYY-MM-DD> - Date of the entry (required)--hours, -h <hours> - Hours worked (required)--note, -n <note> - Description (optional)list-time-entries - List time entries
--from, -f <YYYY-MM-DD> - Start date--to, -t <YYYY-MM-DD> - End dateCredentials: ~/.config/freshbooks/credentials.json
{
"client_id": "op://Private/Freshbooks API/username",
"client_secret": "op://Private/Freshbooks API/credential",
"redirect_uri": "https://localhost/callback"
}
First-time setup:
./scripts/freshbooks-oauth.sh authorize # Get auth URL
./scripts/freshbooks-oauth.sh exchange <code> # Exchange code for tokens
./scripts/freshbooks-oauth.sh me # Test connection
tr containing td.col-timesheet-clientproject\n (Client first, Project second)Time Entry endpoint: POST /timetracking/business/{business_id}/time_entries
Payload:
{
"time_entry": {
"is_logged": true,
"duration": 27000,
"note": "Development",
"started_at": "2026-01-06T09:00:00.000Z",
"project_id": 12447219,
"identity_id": 123456
}
}
duration is in seconds (7.5 hours = 27000 seconds)started_at is the date of the entryproject_id is looked up by project nameidentity_id is the user's FreshBooks identityProject not found: Run ./scripts/freshbooks-api.sh projects to see available project names.
Token expired: The script auto-refreshes tokens, but if it fails, run ./scripts/freshbooks-oauth.sh refresh.
Week mismatch: Make sure you're reading the correct week from Intervals.
1Password CLI: If using op:// references, ensure you're signed in (op signin).