Skip to content

Tidal

The Tidal connector syncs listening history from your Tidal account into the tidal_listens substrate table. The manifest is tier: "core", so the Desktop client surfaces a Tidal configuration card alongside Google + GitHub by default.

  • Substrate-first. Every play lands in tidal_listens unconditionally; sync rules only control the additional routing (folio commits, daily-note injection, harvest). This is the same model as Strava — substrate is always populated so carabase_query_timeseries can answer trend questions even when no rule matches.
  • Multi-account. A workspace can hold multiple Tidal accounts (a personal account + a family member). The OAuth callback rejects cross-account row mutations.
  • Daily + weekly emitters. 30 4 * * * (daily) and 0 5 * * 0 (Sundays, weekly) — pre-aggregated artifact summaries with play count
    • total listening duration. Bucket keys include account_id so a personal + family account can’t collide.
  • Per-account hard-delete. Disconnect via DELETE /api/v1/auth/tidal flips the integration row to is_active=false
    • tokenStatus='revoked'; substrate rows are NOT deleted by default. Use the per-account hard-delete on the Connections page to purge everything (runs the manifest’s purgeAccountSubstrate hook).

See the Tidal OAuth setup guide in the repo for OAuth Developer Application registration, scopes, the PKCE flow, multi-account semantics, and re-auth handling.

tidal-sync cron at 0 * * * * (hourly). Tidal’s recently-played feed updates near-real-time and the listing endpoint is well within the per-app rate budget.

The per-rule filter type TidalFilters lives in src/types/sync-rules.ts. Key fields:

  • minTrackDurationSeconds — drop plays shorter than this. Default 30 (filters out 5-second skips and brief previews). NULL-duration plays are also dropped.
  • excludeArtists — case-insensitive substring match against artist_canonical_name.
  • injectMode"none" / "daily_rollup" / "per_listen". The default "none" keeps per-listen rows in substrate only and lets the daily emitter produce the artifact surface.
  • syncLibrary + libraryFolio — declared on the type for a future opt-in mirror of saved albums / playlists / tracks into a folio README. Not honored by the current sync path — the library-sync branch is explicitly deferred; setting these fields today silently does nothing.

The tidal_listens table carries one row per play event: (workspace_id, account_id, external_id) UNIQUE, plus played_at (timeseries column), track_id, track_title, artist_canonical_name (lowercase, used for matching), album_title, duration_seconds, and raw_payload (full Tidal response). See the database schema reference.