core data

Bank data, in the shape you were going to normalize it into anyway.

Accounts, balances, and transactions on one access token, with a cursor that survives restarts and a transaction object stable enough to store directly. Identity, liabilities, and a net-worth report sit on the same connection.

transactions/sync

The loop you write once.

Page size is 1–500 and defaults to 100. Loop while has_more is true, then keep the final cursor.

# First call: omit the cursor to receive the item's full history as "added".
curl https://connect.feels.money/v1/transactions/sync \
-H "Authorization: Bearer $FEELCONNECT_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"access_token": "fc_at_your_item_token",
"count": 500
}'
  • added — rows you have not seen. On a first sync, that is the entire history.
  • modified — rows you already hold that changed. The common case is a pending authorization posting, where the amount, date, and name can all move. Upsert on transaction_id, which survives the pending → posted transition.
  • removed — ids to delete, typically an authorization that never posted. A removal is never announced for a row you were never sent.
  • next_cursor and has_more — the position to persist, and whether another page is waiting right now.

the transaction object

Twelve fields, stable across every connector.

Whichever upstream served the data, the shape you store is this one.

FieldWhat it is
transaction_idStable id, txn_…. Survives the pending → posted transition, so it is a safe primary key.
account_idThe owning account on the same item.
dateYYYY-MM-DD posted date — the authorization date while the row is still pending.
authorized_datetimeISO-8601 authorization time when the institution reports one, else null.
nameThe raw statement descriptor, unmodified.
merchant_nameCleaned merchant when resolvable — the deterministic descriptor table runs on ingest, not as a separate paid call.
amountMajor units. Positive is money out, negative is money in.
iso_currency_codeISO-4217 code for the amount.
pendingTrue until the transaction posts.
category / category_detailedPrimary and detailed labels from the platform taxonomy — the same strings /v1/categories/get returns. See transaction enrichment.
payment_channelonline, in_store, transfer, or other.

on the same token

What else the connection carries.

No second integration, no second credential — the same access token, gated by the consent the user gave.

  • Identity — names, emails, phones, and addresses on file. Items whose identity sync degraded return the empty identity shape rather than an error. Reference.
  • Liabilities — APR, minimum payment, next payment due, and original principal per credit or loan account. Reference.
  • Assets — total assets, total liabilities, and net worth, computed from balances with holdings standing in only where a balance cannot be trusted, so invested value is never counted twice. Reference.
  • Recurring streams — subscriptions, bills, and payroll detected from the transactions the connector returned, which means they work on any connector that returns transactions at all. How detection works.
  • Investments and crypto — holdings, securities, and trade history where the connector supports them. The full product surface.

What live coverage actually depends on

FeelConnect does not hold bank connections of its own. Every institution in the catalog carries an ordered chain of connectors, and live traffic goes to the first one whose credentials are present in the deployment you are calling.

Some connectors an operator can switch on alone in minutes — GoCardless Bank Account Data (UK and EU banks, free self-signup under their PSD2 licence), Coinbase, Kraken, PayPal, public-address crypto wallets, and CSV / OFX / QFX import. Others need a signed business relationship before they will serve production data, including Plaid, MX, Finicity, Akoya, and Stripe Financial Connections. Where an institution has no enabled connector, a live call returns provider_error rather than pretending.

The sandbox is the exception and it is always on: test keys resolve to the simulated network regardless of credentials, so the full integration is buildable on day one. See going live for the provider-by-provider breakdown.

in production

The parts you have to handle.

Stated plainly so they are not a surprise on launch day.

Rate limits. A token bucket per key, bursting to 3×, with X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset on every response. Exhaustion returns 429 rate_limit_error. Per-second limits and monthly quotas are per plan — see pricing.

Errors. Every non-2xx response is { error: { type, code, message, request_id } }. The request id is also on the successful path, in the dashboard’s request log, so support conversations start from the same row. Error reference.

Broken logins. Items enter login_required or error, fire an item.error webhook, and are repaired by reopening Link in update mode. Build that path before you need it.

faq

Questions developers actually ask.