Skip to main content

CHANGELOG.md

# Changelog

All notable changes to this project will be documented in this file.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [1.0.0] - 2026-06-17

### Fixed (static analysis hardening — Dialyzer/Credo, included in this 1.0.0 cut)

**Dialyzer — contract/spec accuracy**
- Tink.AuthToken.summary/1: tightened the return spec from map() to the
  exact fixed-key shape actually returned (expired, expiry, scope_count,
  scopes, auto_refresh, cache).
- Tink.Cache.stats/0: tightened {:error, any()} to {:error, map()},
  matching Cachex.stats/2's own spec (both tuple positions are map()).
- Tink.HTTP.MutualTLS.finch_pools/0: tightened map() to the precise
  %{String.t() => keyword()} shape returned.
- Tink.HTTP.MutualTLS.child_spec/0: fixed an invalid_contract — the
  declared Supervisor.child_spec() return type is the formal
  %{id:, start: {mod, fun, args}, ...} map shape, but this function
  actually returns a {Finch, opts} 2-tuple meant to be dropped directly
  into a children list (Finch expands it into the real child spec at
  start time). Spec corrected to {module(), keyword()} with a doc note
  explaining the distinction.

**Credo — readability and design**
- Fixed alphabetical alias ordering in test/tink/utils_test.exs
  (alias Tink.{Utils, Client} → alias Tink.{Client, Utils}).
- Tink.Utils.do_poll/5 and Tink.Paginator.stream/2 both nested 3 levels
  deep (if/case/cond and fn/case/if respectively), exceeding the
  configured max of 2. Refactored by extracting the innermost branch into
  separate named helper functions (handle_poll_result/6 +
  wait_and_retry/5 for the former; fetch_page/4 + next_token_or_done/1
  for the latter) — behavior is unchanged, verified by the full test suite.
- Tink.Client's core request pipeline used apply(:request, [...]) on a
  module value with a statically-known arity; replaced with the idiomatic
  module.request(...) dot-call syntax, which is both clearer and friendlier
  to Dialyzer.
- Addressed "nested modules could be aliased at the top of the invoking
  module" (Credo.Check.Design.AliasUsage) across lib/: added missing
  aliases for Tink.Config (application.ex, cache.ex, client.ex,
  http.ex — both adapter modules, webhooks.ex), Tink.RateLimiter
  (application.ex), Tink.Statistics (finance.ex's Tink.CashFlow),
  and Plug.Conn (webhooks.ex's Tink.WebhookVerifier); also fixed one
  redundant fully-qualified Tink.Error.from_response/2 call in
  enrichment.ex where Error was already aliased but not used. lib/ is
  now clean of this warning. Note: test/ files still call most public API
  functions by their fully-qualified name (e.g. Tink.Accounts.list/1) by
  design — these files each exercise many unrelated modules, where the
  fully-qualified name makes it immediately clear which module's behavior
  is under test; this was left as-is rather than mass-aliased.

**Incidental fixes found while addressing the above**
- Tink.Client's hardcoded User-Agent header ("tink-elixir/0.2.0") was
  stale after the version bump to 1.0.0. Replaced with a tink_version/0
  helper that reads Application.spec(:tink, :vsn) at runtime, so it can
  never go stale again.

### Fixed (post-review hardening, included in this 1.0.0 cut)

**Auth (breaking change for these two functions)**
- Tink.Auth.create_authorization/2 and Tink.Auth.delegate_authorization/2
  now send application/x-www-form-urlencoded bodies with snake_case keys
  (user_id, external_user_id, scope, id_hint), matching the real Tink
  API. Previously these sent JSON with camelCase keys, which Tink's
  authorization-grant endpoints reject.
- delegate_authorization/2 now supports the required :actor_client_id
  parameter (previously missing entirely, making delegated grants
  impossible) and an optional :client_id override.

**Application supervision tree (startup-crashing bugs)**
- Fixed Tink.Application's Cachex child spec, which used the :limit
  start option and %Cachex.Limit{} struct removed in Cachex v4. Cache size
  limiting is now configured via the :hooks option with
  Cachex.Limit.Scheduled, per Cachex's v4 migration guide. This previously
  crashed the supervisor on startup whenever cache: [enabled: true].
- Fixed a typo'd Hammer ETS backend option (cleanup_rate_ms →
  cleanup_interval_ms).
- Added the required config :hammer, backend: {Hammer.Backend.ETS, [...]}
  application config — without it, Hammer.check_rate/3 (used by
  Tink.RateLimiter) has no backend to talk to. The Hammer child is now
  only started when rate limiting is actually enabled.
- Note: these bugs were not caught by the test suite because config/test.exs
  disables both caching and rate limiting, so the corresponding supervisor
  children were never started during mix test.

**Webhook signature verification (breaking dependency fix)**
- Tink.WebhookVerifier.verify/3 (and therefore verify_with_config/2) called
  Plug.Crypto.secure_compare/2 directly, but :plug_crypto was never
  declared as a dependency anywhere in mix.exs. This made the *core*,
  non-Plug-specific signature verification function completely broken
  (UndefinedFunctionError) for any consumer that doesn't separately happen
  to have Phoenix/Plug in their dependency tree — e.g. an Oban worker or a
  plain CLI tool processing webhooks. Replaced with a dependency-free
  constant-time comparison (:crypto.hash_equals/2 on OTP 25+, with a
  manual XOR-accumulator fallback for OTP 23/24, which Elixir 1.14 still
  supports). verify_plug/2 still optionally uses Plug.Conn, which is now
  declared as an optional: true dependency.
- This bug was caught by actually running the test suite (mix test)
  against real dependency sources, not just static review — mix.exs was
  also missing elixirc_paths: ["lib", "test/support"], so mix test
  couldn't even compile beforehand. Both are fixed.

**Mutual TLS (Tink.HTTP.MutualTLS)**
- Fixed cert_pem/key_pem/ca_pem handling: :ssl transport options
  require raw DER-encoded binaries for :cert/:cacerts, and {KeyType,
  der} for :key — not decoded public_key records. The previous
  implementation called :public_key.pem_entry_decode/1, which fully
  decodes the PEM entry into an Erlang record (incompatible with :ssl),
  and hardcoded the key type to :RSAPrivateKey even for EC or PKCS#8
  ("PrivateKeyInfo") keys.

**Documentation accuracy**
- Removed an unverified "(max 1000)" page size claim for
  Tink.Transactions.list/2 (/data/v2/transactions); documented the
  confirmed default (10) and max (100) for the related
  /enrichment/v1/transactions endpoint instead.
- Clarified that requests_per_min: 600 in Tink.RateLimiter's example
  config is a conservative client-side default, not a published Tink limit
  — Tink enforces per-app-ID server-side limits without publishing an exact
  number.

### Known caveats (not changed — unverified)
- Tink.WebhookVerifier implements HMAC-SHA256 hex-digest verification
  against an X-Tink-Signature header. This matches the most common
  industry pattern (and could not be contradicted), but the exact header
  name and encoding could not be independently confirmed against Tink's
  current docs in this review. Verify against your Tink dashboard/docs
  before relying on it in production.

### Added

**Auth**
- Tink.Auth.revoke_all/1 — authorization:revoke scope, POST /api/v1/oauth/revoke-all
- Tink.Auth.inspect_token/1 — inspect current token validity and scopes

**Users**
- Tink.Users.get_user/1 — GET /api/v1/user
- Tink.Users.get_profile/1 — GET /api/v1/user/profile
- Tink.Users.update_user/2 — PUT /api/v1/user
- Tink.Users.update_profile/2 — PUT /api/v1/user/profile

**Credentials (new dedicated module)**
- Tink.Credentials.get_qr/2 — GET /api/v1/credentials/{id}/qr
- Tink.Credentials.create/2 — POST /api/v1/credentials
- Tink.Credentials.authenticate/2 — POST /api/v1/credentials/{id}/authenticate
- Tink.Credentials.submit_supplemental_info/3 — POST /api/v1/credentials/{id}/supplemental-information (MFA/BankID flows)

**Accounts**
- Tink.Accounts.get_parties/2 — accounts.parties:readonly
- Tink.Accounts.update/3 — PATCH /api/v1/accounts/{id}
- Tink.Accounts.stream/2 — lazy Stream over all pages

**Transactions**
- Tink.Transactions.get/2 — GET /api/v1/transactions/{id}
- Tink.Transactions.get_similar/2 — GET /api/v1/transactions/{id}/similar
- Tink.Transactions.search/2 — POST /api/v1/search
- Tink.Transactions.suggest/2 — GET /api/v1/transactions/suggest
- Tink.Transactions.update/3 — PUT /api/v1/transactions/{id}
- Tink.Transactions.categorize_multiple/2 — transactions:categorize
- Tink.Transactions.stream/2 — lazy Stream over all pages
- Tink.Transactions.list_all/2 — eager collect all pages

**Providers**
- Tink.Providers.list_markets/1
- Tink.Providers.list_identifiers/1
- Tink.Providers.get_auth_options/2
- Tink.Providers.get_auth_options_for_market/2

**New: Identities**
- Tink.Identities module — identity:read + identities:readonly

**New: Enrichment sub-modules** (replacing unnamed functions)
- Tink.Enrichment.Categories
- Tink.Enrichment.Transactions — with submit_feedback/2
- Tink.Enrichment.Recurring
- Tink.Enrichment.Merchants — get_brand/2, get_merchant/2
- Tink.Enrichment.OnDemand — enrich/2
- Tink.Enrichment.Sustainability — all 8 endpoints

**New: Payments (entirely new domain)**
- Tink.Payments — create, get, get_transfers, cancel, get_conditions, create_settlement_payment, poll_until_terminal
- Tink.MandatePayments — v1 + v2, poll_until_terminal
- Tink.Mandates — get, revoke
- Tink.BulkPayments — create, get
- Tink.SettlementAccounts — accounts, refunds, withdrawals, transactions (full CRUD)

**Budgets (completed)**
- Tink.Budgets.create_one_off/2
- Tink.Budgets.create_recurring/2
- Tink.Budgets.get_details/2
- Tink.Budgets.get_transactions/2
- Tink.Budgets.list_summaries/1
- Tink.Budgets.list_recommended/1
- Tink.Budgets.archive/2

**New: Finance management modules**
- Tink.SavingsGoals — full CRUD + archive, complete, allocations, deposit, withdraw, reallocate
- Tink.Subscriptions — list, get_transactions, update
- Tink.CostOfLiving — list, get_transactions, update
- Tink.Insights — list, list_archived, archive, take_action

**RiskInsights (completed)**
- Tink.RiskInsights.create/2
- Tink.RiskInsights.delete/2

**AccountCheck**
- Tink.AccountCheck.get_pdf/2

**New: BalanceCheck / BalanceRefresh**
- Tink.BalanceCheck.trigger_refresh/2
- Tink.BalanceCheck.get_refresh_status/2
- Tink.BalanceCheck.poll_until_complete/3

**New: Connectivity**
- Tink.Consents — full v2 consent lifecycle (create, list, get, revoke, authorizations, relay, templates)
- Tink.ProviderConsents — list, extend

**Connector (completed)**
- Tink.Connector.upsert_account/4 — single account upsert
- Tink.Connector.delete_accounts/2 — v2 batch delete
- Tink.Connector.delete_transactions/3
- Tink.Connector.batch_delete_transactions/2
- Tink.Connector.batch_update_transactions/2
- Tink.Connector.upsert_transactions_v2/2
- Tink.Connector.get_operation/2
- Tink.Connector.poll_operation/3

**Webhooks (completed)**
- Tink.Webhooks — create, list, get, update, delete webhook endpoints
- Tink.WebhookHandler — event registry with GenServer dispatcher, known_events/0
- Tink.WebhookVerifier — verify/3, verify_with_config/2, verify_plug/2

**New: Infrastructure modules**
- Tink.ReportJobs — get, poll_until_complete
- Tink.TransactionReports — get
- Tink.Merchants — create, list, get
- Tink.Paginator — stream/2, collect_all/2

**Infrastructure improvements**
- Full-jitter exponential backoff retry on 429/503/network errors
- Telemetry events: [:tink, :request, :start/stop], [:tink, :cache, :hit/miss]
- Tink.Client.expired?/1 for token lifetime checks
- Tink.Client.add_query/2 utility for building query strings

### Fixed
- License: unified to Apache-2.0 throughout (README, mix.exs, LICENSE)
- Tink.Users.create_authorization/2 removed (use Tink.Auth.create_authorization/2)
- Transactions module split collapsed — single Tink.Transactions with pagination options

---

## [0.1.1] - 2024-11-01

Initial published release.