All notable changes to Tink are documented here.
The format follows Keep a Changelog. Tink uses Semantic Versioning.
[Unreleased]
[0.1.1] - 2026-03-11
Three days of fixes, refactoring, cache integration, static analysis cleanup, and project scaffolding on top of the empty 0.1.0 skeleton.
March 9 — Initial SDK Implementation
All 35 source files written from scratch, implementing the full Tink API surface.
Core infrastructure
Tink.Application— OTP application, Finch pool supervision, Cachex child specTink.Client— HTTP client struct, token attachment, cacheable-pattern detectionTink.Config— runtime config reader with typed accessorsTink.Auth/Tink.AuthToken— OAuth 2.0 client credentials and authorization code flowsTink.Error— unified error struct (status,code,message,request_id)Tink.HTTPBehaviour— behaviour contract for HTTP adaptersTink.HTTPAdapter— Finch-backed implementation with request/response encodingTink.Retry— exponential backoff with jitter for 429 / 503 / network errorsTink.RateLimiter— per-key rate limiting via HammerTink.Cache— Cachex wrapper with TTL helpers and user-scoped invalidationTink.Helpers— shared URL building and query encoding utilitiesTink.Connector— provider connection utilitiesTink.WebhookHandler— event dispatch and handler registrationTink.WebhookVerifier— HMAC signature verification
Domain modules
Tink.Accounts,Tink.Users,Tink.Categories,Tink.StatisticsTink.Transactions,Tink.TransactionsOneTimeAccess,Tink.TransactionsContinuousAccessTink.AccountCheck,Tink.BalanceCheck,Tink.BusinessAccountCheckTink.IncomeCheck,Tink.ExpenseCheckTink.RiskInsights,Tink.RiskCategorisationTink.Investments,Tink.LoansTink.Budgets,Tink.CashFlow,Tink.FinancialCalendarTink.Providers,Tink.Connectivity,Tink.Link
March 10 — Bug Fixes, Security, and Structural Refactoring
Fixed — Critical Bugs
link.ex—add_test_params/2:paramsvariable was unbound, causing a compile errorstatistics.ex— Duplicatedefmodule Tink.Statisticsblock (lines 367–end) caused a compile conflict; removedbalance_check.ex—build_consent_update_link/2: URL was missing thehttps://scheme, producing a malformed linkconnector.ex— String interpolation bug: literal"demo-scenario_str"was never interpolated; corrected to"demo-#{scenario_str}"client.ex—do_delete/3had two contradictory success match clauses; unified to consistently return{:ok, body}or{:ok, %{}}, matching all other HTTP verb helpersusers.ex—delete_credential/2@specdeclared return type as:ok; corrected to{:ok, map()} | {:error, Error.t()}
Fixed — Security
webhook_verifier.ex— Replaced non-constant-timesecure_compare/2with:crypto.hash_equals/2to eliminate a timing side-channel vulnerability in webhook signature verification
Fixed — Correctness
config.ex— Rate limit config key mismatch: code read from nested:rate_limitbut docs specified flat:enable_rate_limiting; aligned to flat key throughoutwebhook_handler.ex—validate_payload/1was defined but never called; added to thewithpipeline so invalid payloads are rejected before dispatchwebhook_handler.ex— Test webhooks from the Tink console were being dispatched as:unknownevents; addedcheck_not_test_webhook/1guard to intercept and acknowledge them without dispatchcache.ex— Deadpatternvariable ininvalidate_user/1; prefix was bareuser_idwhich could match unintended keys — corrected to"#{user_id}:"to scope invalidation precisely
Changed — Design
webhook_handler.ex+application.ex— ReplacedApplication.put_envhandler registry with an ETS:bagtable (:tink_webhook_handlers) created at application start; concurrent-safe, survives config reloads, supports multiple handlers per event type
Changed — Structure
account_check.ex,balance_check.ex— Replaced duplicate auth, user, and credential functions (copied verbatim fromTink.Users/Tink.Accounts) withdefdelegate— single source of truth, eliminates maintenance divergence risktransactions.ex— Replaced duplicatelist_accountsimplementation withdefdelegate list_accounts(client, opts \\ []), to: Tink.Accountsproviders.ex— Removed unusedCachealias; replaced privatebuild_url/build_query_paramshelpers withHelpers.build_url; added explicitCache.fetch/3for cacheable readsclient.ex— Rewrotecacheable?/1using module-level@cacheable_patternsand@non_cacheable_patternscompile-time constants; fixeddetect_resource_type/1ordering so specific patterns are matched before catch-all onesretry.ex—default_retry?/1now delegates toshould_retry?/1instead of duplicating logic;log_no_retry/2gated onConfig.debug_mode?()to matchlog_retry/2symmetricallyusers.ex— Removed redundantCache.invalidate_user/1call inrefresh_credential/2— invalidation already handled by the underlying credential update path
March 11 — Cache Integration, Dialyzer, Best Practices, Project Config
Added — Cache Integration
Explicit Cache.fetch/3 added to all cacheable read operations across seven
domain modules. Each module owns its own cache keys and TTLs; Client.get
automatic caching is retained only as a fallback. All cache calls pass
cache: false to Client.get to prevent double-caching. Cache is skipped
when client.cache is falsy or Cache.enabled?() returns false.
Cache keys follow the pattern "scope:resource:qualifier" with "list" /
"item" disambiguators to prevent key collisions between list and single-item
queries.
| Module | Functions | TTL |
|---|---|---|
Tink.Providers | list_providers/2, get_provider/2 | 1 hour / 2 hours |
Tink.Categories | list_categories/2, get_category/3 | 24 hours |
Tink.Accounts | list_accounts/2, get_account/2 | 5 minutes |
Tink.Accounts | get_balances/2 | 1 minute |
Tink.Statistics | get_statistics/2, get_category_statistics/3, get_account_statistics/3 | 1 hour |
Tink.Investments | list_accounts/1, get_holdings/2 | 5 minutes |
Tink.Loans | list_accounts/1, get_account/2 | 5 minutes |
Tink.Budgets | get_budget/2, list_budgets/2 | 5 minutes |
Tink.Budgets | update_budget/3 | Invalidates cache on success |
Fixed — Dialyzer
account_check.ex:357—is_binary(binary)guard on a value typed asmap()can never succeed; removed the unreachable guard and split the PDF-map and plain-map branchesapplication.ex—@transport_optsmodule attribute cannot store the closure returned by:public_key.pkix_verify_hostname_match_fun/1(Elixir only escapes static terms); replaced with a compile-timeif @env == :prod do … endblock emitting twodefp transport_opts/0clauses — eliminates theArgumentErrorwhile retaining compile-time branch selectionapplication.ex—case @env dowith a compile-time@envcaused dead-branch warnings for non-current environments; replaced with@pool_countand@default_pool_sizecompile-time module attributes to eliminate the branching entirelyauth.ex:258— ReferencedTink.health_check/1which does not exist; replaced withClient.get(client, "/api/v1/user", cache: false)balance_check.ex:68—alias Tink.Helperswas unused; removedconnectivity.ex:505—{:error, reason}match clause was unreachable given the success type of the preceding call; removedhttp_adapter.ex:229—decode_response(%{body: _body})fallback unreachable because Finch always returns a binary body; removed dead clauserate_limiter.ex— Full rewrite: bareHammer.check_rate/3,inspect_bucket/3,delete_buckets/1calls (Hammer v5/v6 API that no longer exists in 7.x) replaced with Hammer 7.2use Hammer, backend: :ets, algorithm: :fix_windownamed-backend pattern usingBackend.hit/3andBackend.get/2; backend process added toTink.Applicationsupervision tree
Changed — Best Practices
client.ex— Removed unusedConfigandHTTPAdapteraliaseshttp_adapter.ex— Added@specto all 6@impl truebehaviour callbacksapplication.ex— Added@spec start(Application.start_type(), term()) :: {:ok, pid()} | {:error, term()}error.ex— ReplacedIO.inspect(error)in@moduledocexample withLogger.error/1- 18 domain modules — Added 52 missing
@specannotations to public helper functions categories.ex,accounts.ex— Added"list"/"item"cache key disambiguators to eliminate collision risk between list-query and single-item cache entries
Added — Project Scaffolding
mix.exs— Corrected app name:tink→:tink; module prefixTink.*→Tink.*throughout; added:public_keytoextra_applications;groups_for_modulesupdated with all actual module names and a new Webhooks group;extrasexpanded to cover all guide paths.formatter.exs— Setline_length: 120; addeddefdelegate: 2tolocals_without_parens.credo.exs—strict: true;MaxLineLengthaligned to 120; per-check rationale comments for all disabled entries;includedpaths tightened for a single-app library.dialyzer_ignore.exs— Documented suppressions for all 5 known benign Dialyzer warningsREADME.md— Hex badge, feature overview, quick-start snippet, full product table, configuration referenceLICENSE— MIT- 19 documentation guides across
guides/,guides/products/, andguides/advanced/
[0.1.0] - 2025-03-01
Initial release — empty Elixir package published to Hex.pm to reserve the
tink package name.