Card spending#
Each card purchase notice — your bank's webhook, or the text message it sends you, forwarded by your phone — goes to the workflow's address. A model reads the amount, the shop and the date; a classification files the purchase (food, transport, health, home, leisure, other); a condition compares it with your limit; and a purchase over the limit is told to you on your phone and Telegram. Every box is light: an execution takes seconds and starts no container.
This is the Card spending recipe of the console. Your first workflow builds it in the console; this page writes it as a file and connects it to real notices.
flowchart LR
T([bank<br/>HTTP call · dedupe by id]) --> R[read<br/>model · JSON]
R --> C[category<br/>classify]
R --> B{big}
B -- over --> N[tell<br/>notify]
C --> N
Before you begin#
- An assistant or a
deployment of the workspace that
answers chat. Below,
<assistant>stands for its name; for a deployment, writedeployment:instead ofassistant:. - The editor role, and
astrasigned in and pointed at the workspace (Install the CLI). - To be told on your phone, the app's notifications; on Telegram, your Telegram connected to an assistant.
1. Write the workflow#
apiVersion: anemoi.astralyx/v1
kind: Flow
metadata:
name: card-spending
spec:
description: Reads each card purchase, files it, tells me when it is over my limit.
inputs:
- {name: limit, description: Tell me about purchases from this amount, default: "200"}
triggers:
- {id: bank, kind: http, dedupe: "${{ event.id }}", dedupe_seconds: 600}
concurrency: {max: 2}
steps:
- id: read
kind: model
assistant: <assistant>
format: json
prompt: "Read this card purchase notice and answer with JSON: amount (a number), merchant, date (YYYY-MM-DD).\n\n${{ event.text }}"
- id: category
kind: classify
needs: [read]
assistant: <assistant>
input: "${{ steps.read.outputs.merchant }}"
labels:
- {name: food, description: "groceries, restaurants, bakeries"}
- {name: transport, description: "fuel, rides, parking, transit"}
- {name: health, description: "pharmacy, doctors, gym"}
- {name: home, description: "rent, bills, repairs, furniture"}
- {name: leisure, description: "travel, streaming, shows, games"}
- {name: other, description: anything else}
- id: big
kind: switch
needs: [read]
cases:
- {name: over, when: "steps.read.outputs.amount >= inputs.limit"}
default: under
- id: tell
kind: notify
needs: [big, category]
when: steps.big.outputs.branch == 'over'
title: "R$ ${{ steps.read.outputs.amount }} at ${{ steps.read.outputs.merchant }}"
message: "Category: ${{ steps.category.outputs.label }}. Over your limit of R$ ${{ inputs.limit }}."
outputs:
amount: "${{ steps.read.outputs.amount }}"
merchant: "${{ steps.read.outputs.merchant }}"
category: "${{ steps.category.outputs.label }}"
What to notice:
readanswers JSON. Withformat: json, each field of the model's object is an output of its own:steps.read.outputs.amount. A model that answers prose fails the attempt, and the box tries twice more.bigcompares numbers.amount(250) andlimit(200) both read as numbers, so>=compares them as numbers. Change the limit for one execution with an input, or for all with the input'sdefault.tellwaits forcategory, so the notice can name it, and runs only on the wayover. Under the limit it is skipped and the execution still succeeds.- One execution per purchase. Notices carry an
id; the trigger remembers each for ten minutes, so a sender that retries does not start a second execution. - Two at a time.
concurrency: {max: 2}: a burst of notices waits its turn instead of asking the model ten things at once. tellhas noto: it tells the person the workflow runs as — for calls to the address, whoever made it.
2. Apply it and make its address#
$ astra iris workflows apply -f card-spending.yaml
flow card-spending: version 1 written, now current
Then, in the console: Iris → Workflows → card-spending → Triggers,
Make the address under bank, and copy the URL — its secret is shown
once. (With the API:
Make its address.)
3. Try it#
Below, <address> stands for the URL you copied.
$ curl -sS -X POST '<address>' -H 'Content-Type: application/json' \
-d '{"id":"t1","text":"COMPRA APROVADA R$ 250,00 PADARIA SAO JOAO"}'
{"execution":"card-spending-3f9a1c","state":"Pending","deduplicated":false}
$ astra iris workflows get card-spending-3f9a1c
get shows read with amount=250 and merchant=PADARIA SAO JOAO among
its outputs, category with label=food, big with branch=over, and tell
Notified; your phone shows the notice.
4. Feed it real notices#
What the workflow reads is event.text (and event.id, to drop repeats):
anything that sends that to the address works.
- A bank or card that sends webhooks: give it the address. When it
signs its webhooks, set the trigger's
auth: hmacand itssignature_headerto the bank's header, and give the bank the secret (Signature). When its body is not{"id", "text"}, change the prompt to read its fields (${{ event.description }}) anddedupeto its id. - The text message your bank sends you: an automation on your phone
that sends each such message to the address — an iPhone Shortcut with
Get Contents of URL (as in A health report),
or an Android automation app that makes an HTTP request — with the body
{"text": <the message>}. Without anid, nothing is deduplicated. - A form or a script: any HTTP client, as
curlabove.
Make it yours#
- Keep a record. Add a Function box
after
categorythat writes the purchase where you keep your spending; its inputamount = ${{ steps.read.outputs.amount }}arrives as a number. - Ask when unsure. A classification below 0.6 confidence answers
uncertain: add a condition onsteps.category.outputs.labeland a notify box that asks you. - Tell someone else.
to: ["user:<id>"]ontell, for each person of the organisation to tell.
Clean up#
On the workflow's page, Triggers → Remove the address, then Delete. Its executions stay until you delete them.