Hey Siri, add a transaction to YNAB

That was an interesting journey.

I'd never really invested the time in creating my own really complicated iOS Shortcut from scratch before. The limited debugging options and figuring out how it all worked, step by step by step, reminded me a bit of my early days programming as a kid, bewildered but steadily figuring it out.

Okay, but to back up for a minute: my goal was to teach Siri how to record a transaction in You Need a Budget—YNAB, for short.

There were, of course, already-written solutions. But I wanted to try to write my own, diving down into what it actually took to do this.

YNAB has some pretty poor Shortcuts support already. It'll let you re-record something you already recorded, or it'll jump you straight into the YNAB "add transaction" window so you can type things. But I wanted to see if I could do it entirely voice-driven.

YNAB also has an API. And, though I've never really mucked about with it before, Shortcuts has the capability to interact with REST-ish APIs. So I dived in.

Once I got my personal access token, I started mucking about with the "Get contents of URL" action. I needed a couple things:

  • A URL. I set a variable called "API base URL" to http://api.youneedabudget.com/v1 by using the "Text" action and the "Set variable" action. I'd then use the URL action to concatenate variables and strings together to create a URL to make requests to.
  • An Authorization header for the API. I added this to Headers, with the word "Bearer" followed by my token from a variable (also created with "Text" and "Set variable").

I started by hitting the GET /budgets endpoint, because the very first thing I needed was a budget id. That returns JSON. I only have one budget, so I grabbed "data.budgets" with "Get Dictionary Value" action, grabbed the first item with "Get Item From List", and then finally "id" from that dictionary.

(Yes, I know "I only have one budget" is asking for trouble. I'll catch up with that problem later, I swear.)

Armed with the budget id, I now needed a list of accounts. The resulting list (in data.accounts) looked like something I needed to map over, since I wanted to present names to the user and ids to me.

This was the first slow-going part of the experiment. Eventually, I discovered that the "Repeat with Each" action is basically a map. Each item is available inside the loop, and whatever you "return" at the end of each iteration becomes part of a new list.

About this time, my iPad unceremoniously dumped me back to the Shortcuts main screen, with work thus far nowhere in sight. I gritted my teeth, established a habit of backing out to the main screen and duplicating my current work every so often, and pushed ahead.

Once I'd re-established where I was, I discovered another problem: It seemed I could feed a list of names to "Choose From List", but the output was just a name.

There didn't appear to be a find operation that would get me back through the list of accounts so I could get the matching id, so I tried to build one. It never worked, and I'm honestly still not clear why. Sometimes it would freeze up; other times, Shortcuts would inform me a "problem" occurred.

Back to the docs. I found out that you could actually pass "Choose From List" a dictionary, with the names as keys and ids as values, in this case. So I set about building a dictionary in a reduce-ish operation—I started by creating an empty one with the "Dictionary" action, then looped through the list setting keys and values.

The resulting dictionary I could feed to "Choose From List". It showed the names as titles and the ids as detail text, but that was ok. The choose result always looked like this, too… but when I assigned it to a variable and used that later, it went into the request as the id. Okay then.

I used the "Ask for Input" action to prompt the user to supply a payee and an amount. This worked great in testing, because there were always dialogs to type into. But when I asked Siri to run the shortcut, they got hopelessly confused asking for the numeric input for the amount, thanks to an apparent bug in 16.2. I think I've figured out how to make it happy, though.

Actually POSTing the transaction looked straightforward enough: set "Get Contents of URL" to POST mode, then tell it the request was a dictionary. Right? Except now I ran into the problem that this particular dictionary editor was hopelessly broken.

So, instead, I used the "Dictionary" action once more to build a "transaction" dictionary containing keys for the account id, amount, and date (used the "Date" and "Format Date" actions to get YYYY-MM-DD for this), and finally the payee name.

Then I stuffed that using another "Dictionary" action (start blank, then set key) into the "transaction" key. (Not quite an API turducken, but getting there.)

Then, that went into the request itself, as a "file". It didn't make a lot of sense to me that way, but it worked. Okay then.

While iterating, I didn't want to keep creating too many transactions, so instead of submitting directly to YNAB's API, I instead submitted to a tiny Express server on my Mac mini that would output the result of requests to the console. This is the entire server:

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use((req, res) => {
        console.log(req.headers);
        console.log(req.body);
        res.sendStatus(200);
});
app.listen(3000, () => console.log('ready'));

Once I was happy with the result, I started posting transactions to the API. Noticed a few things.

First, YNAB's amounts seem to need to be multiplied by 1,000, i.e. $12.34, to submit to the API, is 12340. Easily rectified with a little "Calculate" action.

Second and more seriously, it wasn't clear to me how to indicate success or failure. It didn't seem like anything special happened Shortcut-wise if the YNAB API didn't like my POST. (I still don't know if I can even see an HTTP result code in a Shortcut.)

So I did some parsing of the response from the POST. I grabbed the "data.transaction" object, and if it existed, I extracted the amount (dividing by 1,000 again), date, and payee and displayed them in a "Show Alert" action. If it didn't exist, I dumped the response in a different "Show Alert" action indicating failure.

Anyway, here's the final result. Siri's a little… iffy on understanding my payees as dictated, but I think it'll get me in a better habit of putting stuff in YNAB ahead of it syncing with the banks. Especially for accounts that don't work.


You'll only receive email when they publish something new.

More from Mattie B
All posts