Skip to main content

Insights

Overview

Insights are AI-generated financial observations and recommendations produced by the API when strategy runs analyze a user’s transaction history. Each insight contains a structured payload with a title, body text, and optional recommended action. Insights come in two forms:
  • Passive insights — informational observations (e.g. “Your grocery spending increased 18% this month”). No user action required.
  • Actionable insights — recommendations with a call to action (e.g. “Switch your subscription billing to your rewards card to save $12/month”). The user can accept or dismiss these.
Insights are generated asynchronously during strategy runs. Once created, they appear in the user’s insight feed and can be filtered, expanded with related transaction data, and acted upon.

Quick Start

1. List insights

curl /v0/insights/ \
  -H "x-juno-jwt: <token>"
Returns a paginated list of insights for the authenticated user (200 OK).

2. Read a single insight with transactions

curl "/v0/insights/42?expand=transactions" \
  -H "x-juno-jwt: <token>"
Returns the insight details with the transactions that triggered it (200 OK).

3. Accept an actionable insight

curl -X POST /v0/insights/42/accept \
  -H "x-juno-jwt: <token>"
Transitions the insight to accepted status (200 OK).

4. Dismiss an actionable insight

curl -X POST /v0/insights/42/dismiss \
  -H "x-juno-jwt: <token>"
Transitions the insight to dismissed status (200 OK).

Insight Model

Each insight contains:
{
  "insight": {
    "id": 42,
    "user_strategy_run_id": 7,
    "call_to_action": true,
    "status": "pending",
    "actioned_at": null,
    "payload": {
      "title": "Reduce subscription overlap",
      "body": "You have two active streaming subscriptions billed within 3 days of each other. Consolidating could save you money.",
      "icon": "credit-card",
      "category": "spending",
      "expected_benefit": {
        "amount": 1499,
        "period": "monthly",
        "label": "Save $14.99/mo"
      },
      "action": {
        "type": "cancel_subscription",
        "params": { "subscription_id": "sub_abc123" }
      },
      "if_accepted": "We'll flag this subscription for cancellation review."
    },
    "created_at": 1706140800000,
    "transactions": null
  },
  "_links": {
    "self": { "href": "/v0/insights/42" },
    "user": { "href": "/v0/user/me" },
    "transactions": { "href": "/v0/insights/42?expand=transactions" },
    "strategy-run": { "href": "/v0/user/strategies/str_ext_1/runs/run_ext_7" },
    "strategy": { "href": "/v0/user/strategies/str_ext_1" },
    "accept": { "href": "/v0/insights/42/accept", "method": "POST" },
    "dismiss": { "href": "/v0/insights/42/dismiss", "method": "POST" }
  }
}
FieldTypeDescription
idintegerUnique insight identifier
user_strategy_run_idinteger or nullThe strategy run that generated this insight
call_to_actionbooleantrue if the insight is actionable, false if passive
statusstringCurrent lifecycle state (see Status Values)
actioned_atinteger or nullEpoch milliseconds when the user accepted or dismissed
payloadobject or nullStructured insight content (see Payload Types)
created_atintegerEpoch milliseconds when the insight was generated
transactionsarray or nullRelated transactions (only present when ?expand=transactions is used)

Payload Types

Insight payloads follow one of two schemas depending on how the insight was generated. AI Insight Payload — generated by strategy runs analyzing transaction data:
FieldTypeDescription
titlestringShort headline for the insight
bodystring or nullDetailed explanation
iconstring or nullIcon identifier for display
categorystring or nullTopic category (e.g. "spending", "savings")
expected_benefitobject or nullProjected savings — contains amount (integer, minor units), period, and label
actionobject or nullRecommended action — contains type (string) and optional params (object)
if_acceptedstring or nullHuman-readable description of what happens on accept
Onboarding CTA Payload — generated during onboarding to guide users toward features:
FieldTypeDescription
insightstringThe insight message
call_to_actionstring or nullCTA button text
app_actionstring or nullIn-app navigation target (open_banking_connect, create_budget, explore_strategies)
sourcestring or nullOrigin identifier

Endpoints

List Insights

GET /v0/insights/
Returns a paginated list of the authenticated user’s insights, ordered by creation date (newest first). Query parameters:
ParameterTypeDefaultDescription
call_to_actionbooleanFilter by actionable (true) or passive (false)
statusstringFilter by status: pending, accepted, dismissed, expired
offsetinteger0Pagination offset
limitinteger20Results per page (1—100)
Response: 200 OK
{
  "insights": [
    {
      "id": 42,
      "user_strategy_run_id": 7,
      "call_to_action": true,
      "status": "pending",
      "actioned_at": null,
      "payload": {
        "title": "Reduce subscription overlap",
        "body": "You have two active streaming subscriptions...",
        "category": "spending"
      },
      "created_at": 1706140800000,
      "transactions": null
    },
    {
      "id": 41,
      "user_strategy_run_id": 7,
      "call_to_action": false,
      "status": "pending",
      "actioned_at": null,
      "payload": {
        "title": "Grocery spending up 18%",
        "body": "Your grocery spending increased compared to the previous month.",
        "category": "spending"
      },
      "created_at": 1706140700000,
      "transactions": null
    }
  ],
  "total": 2,
  "offset": 0,
  "limit": 20,
  "_links": {
    "self": { "href": "/v0/insights?offset=0&limit=20" },
    "user": { "href": "/v0/user/me" }
  }
}
Pagination links (prev, next) appear when applicable and preserve any active filters.

Get Insight

GET /v0/insights/{insight_id}
Returns a single insight by ID. Query parameters:
ParameterTypeDefaultDescription
expandstring (repeated)Pass expand=transactions to include related transaction data
Response: 200 OK
{
  "insight": {
    "id": 42,
    "user_strategy_run_id": 7,
    "call_to_action": true,
    "status": "pending",
    "actioned_at": null,
    "payload": {
      "title": "Reduce subscription overlap",
      "body": "You have two active streaming subscriptions billed within 3 days of each other.",
      "category": "spending",
      "expected_benefit": {
        "amount": 1499,
        "period": "monthly",
        "label": "Save $14.99/mo"
      },
      "action": { "type": "cancel_subscription", "params": { "subscription_id": "sub_abc123" } },
      "if_accepted": "We'll flag this subscription for cancellation review."
    },
    "created_at": 1706140800000,
    "transactions": [
      {
        "external_id": "txn_ext_101",
        "type": "debit",
        "status": "settled",
        "direction": "outgoing",
        "source": "bank",
        "amount": 1499,
        "merchant_name": "StreamCo",
        "category": "entertainment",
        "created_at": 1706054400000,
        "updated_at": 1706054400000
      }
    ]
  },
  "_links": {
    "self": { "href": "/v0/insights/42" },
    "user": { "href": "/v0/user/me" },
    "transactions": { "href": "/v0/insights/42?expand=transactions" },
    "accept": { "href": "/v0/insights/42/accept", "method": "POST" },
    "dismiss": { "href": "/v0/insights/42/dismiss", "method": "POST" }
  }
}

Accept Insight

POST /v0/insights/{insight_id}/accept
Marks an actionable insight as accepted. Sets status to "accepted" and records actioned_at. Response: 200 OK — returns the updated insight.
Only insights with call_to_action: true and status: "pending" can be accepted. Attempting to accept a passive insight returns 400 Bad Request. Attempting to accept an already-actioned insight returns 409 Conflict.

Dismiss Insight

POST /v0/insights/{insight_id}/dismiss
Marks an actionable insight as dismissed. Sets status to "dismissed" and records actioned_at. Response: 200 OK — returns the updated insight.
The same constraints apply as accept: the insight must have call_to_action: true and status: "pending". Non-actionable insights return 400, already-actioned insights return 409.

Integration Patterns

Building an Insight Feed

  1. Fetch insights with GET /v0/insights/?limit=20
  2. Render each insight using payload.title and payload.body
  3. For insights where call_to_action is true and status is "pending", show accept/dismiss buttons
  4. When the user taps accept or dismiss, call the corresponding endpoint
  5. The response contains the updated insight — re-render in place
insights = GET /v0/insights/?limit=20

for insight in insights:
    render(insight.payload.title, insight.payload.body)
    if insight.call_to_action and insight.status == "pending":
        on_accept → POST /v0/insights/{id}/accept
        on_dismiss → POST /v0/insights/{id}/dismiss

Filtering Actionable Insights

To show only pending items that need user attention:
curl "/v0/insights/?call_to_action=true&status=pending" \
  -H "x-juno-jwt: <token>"

Expanding Transactions

When displaying insight detail, expand the related transactions to show what triggered the insight:
curl "/v0/insights/42?expand=transactions" \
  -H "x-juno-jwt: <token>"
Each transaction in the array includes asset, wallet, account, and card data when available. Actionable insights include _links to the strategy and strategy run that generated them. Use these to let users view the full analysis context:
  • _links.strategy — the strategy configuration
  • _links.strategy-run — the specific run that produced this insight
These links are present when the insight was generated by a strategy run (i.e. user_strategy_run_id is not null).

Handling Onboarding CTAs

During onboarding, the API may generate insights with OnboardingCtaPayload containing an app_action field. Use this to route the user to the appropriate in-app screen:
app_actionDescription
open_banking_connectNavigate to the bank connection flow
create_budgetNavigate to budget creation
explore_strategiesNavigate to the strategy catalog

Error Handling

All error responses follow RFC 7807 Problem Details format:
{
  "type": "https://api.sumvin.com/errors/ins-404-001",
  "title": "Insight Not Found",
  "status": 404,
  "detail": "Insight 42 not found",
  "instance": "/v0/insights/42",
  "error_code": "INS-404-001"
}
StatusError CodeMeaningRecovery
400 Bad RequestINS-400-001Insight is not actionable (passive insights cannot be accepted/dismissed), or invalid status filterCheck call_to_action is true before attempting accept/dismiss
401 UnauthorizedMissing or invalid authentication tokenRe-authenticate
403 ForbiddenINS-403-001Insight belongs to a different userVerify the insight ID belongs to the authenticated user
404 Not FoundINS-404-001Insight does not exist or has been deletedVerify the insight ID; re-fetch the insight list
409 ConflictINS-409-001Insight has already been accepted or dismissedCheck status before attempting to act; no further action needed

Reference Tables

Status Values

ValueDescription
pendingInsight has not been acted upon
acceptedUser accepted the recommended action
dismissedUser dismissed the insight
expiredInsight expired without user action
LinkMethodConditionDescription
selfGETAlwaysFetch this insight
userGETAlwaysFetch the owning user
transactionsGETAlwaysFetch insight with expanded transactions
strategy-runGETWhen generated by a strategy runFetch the originating strategy run
strategyGETWhen generated by a strategyFetch the strategy configuration
acceptPOSTWhen call_to_action is true and status is "pending"Accept the insight
dismissPOSTWhen call_to_action is true and status is "pending"Dismiss the insight

Query Parameters (List Endpoint)

ParameterTypeDefaultDescription
call_to_actionbooleantrue for actionable, false for passive
statusstringOne of: pending, accepted, dismissed, expired
offsetinteger0Number of items to skip
limitinteger20Items per page (1—100)

Next Steps