Skip to content

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#

1. Write the workflow#

card-spending.yaml
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:

  • read answers JSON. With format: 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.
  • big compares numbers. amount (250) and limit (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's default.
  • tell waits for category, so the notice can name it, and runs only on the way over. 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.
  • tell has no to: 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: hmac and its signature_header to 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 }}) and dedupe to 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 an id, nothing is deduplicated.
  • A form or a script: any HTTP client, as curl above.

Make it yours#

  • Keep a record. Add a Function box after category that writes the purchase where you keep your spending; its input amount = ${{ steps.read.outputs.amount }} arrives as a number.
  • Ask when unsure. A classification below 0.6 confidence answers uncertain: add a condition on steps.category.outputs.label and a notify box that asks you.
  • Tell someone else. to: ["user:<id>"] on tell, 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.