Chapters
Reaching the outside world: connectors and API
A Catena sequence lives inside the calculator. On its own it can add, compare, loop, and remember — but it cannot read a file on your disk, open a network connection, or drive a device. That is deliberate. When a sequence does need something from the outside world — a live exchange rate, a custom pricing service, a receipt printer — it asks for it through one single, controlled door: the API command. This chapter explains that door: why it is the only one, how you write it, how friendly commands like CURRENCY are built on top of it, and what happens when a call cannot complete.
If you only ever convert currency, read the first two sections and then The worked example. If you are writing sequences that talk to your own services, read all the way through.
The safety principle: one door out
Catena is safe by construction. A sequence has no general access to the file system, the network, or any device. There is exactly one way for a running sequence to reach anything outside its own calculation, and that is the API command.
This is not a limitation to work around — it is the point. Because API is the only exit, you can always tell, just by reading a sequence, everything it could possibly touch on the outside: look for the API calls (and the friendly commands that stand in for them). A sequence with no API calls is completely self-contained. It will compute the same answer on any machine, with or without a network, today or next year.
What lies beyond that door is not open-ended either. API cannot reach arbitrary URLs or files. It can only reach a connector — a capability that Castiel itself chooses to publish and that you have configured. A connector exposes exactly the functions the host decides to offer (converting a currency, calling your endpoint, printing a receipt) and nothing more. If a capability has no connector, no sequence can reach it. You are always in control of what the door opens onto.
Connectors are set up once, outside of any sequence, in Settings -> Connectors. That is where you enter a currency rate source, add a custom endpoint and its key, or configure a printer. A sequence never carries a password or a URL in its own text; it names a connector, and the connector — which you configured — holds the details. See Connectors for the full setup walkthrough.
The generic form: API(connector, function, ...)
Every outside call has the same shape. You name the connector, name the function you want from it, and then pass whatever arguments that function needs. The result comes back as a value you assign with the -> arrow, and it takes on its natural type on assignment.
API ("checkage", "isAdult", userAge) -> isAdultReading left to right: call the "checkage" connector, invoke its "isAdult" function, pass userAge, and store the answer in isAdult. The arguments can be plain values or a Bag of values; the return is typed when you assign it.
The connector name and the function name are text in quotes because they are looked up by name in the connectors you have configured. Everything after them is ordinary Catena — variables, numbers, or expressions you have already computed.
That is the whole mechanism. There is nothing else to learn about reaching outside: one command, always API(connector, function, ...).
Friendly commands are shorthand over API
Writing API("currencyrates", "convert", ...) every time you convert money would be tedious and easy to get wrong. So Castiel ships domain commands — readable names that stand in for a specific API call. CURRENCY is the built-in example:
CURRENCY ("USD", priceUsd, "EUR") -> itemPriceThat single line is exactly the same as writing:
API ("currencyrates", "convert", "USD", priceUsd, "EUR") -> itemPriceCURRENCY(from, amount, to) is simply a nicer spelling of API("currencyrates", "convert", from, amount, to). It is not a separate feature with its own rules — it is an API call, wearing a friendlier name. Everything in this chapter about the safety boundary and about failure recovery applies to CURRENCY and to every other domain command, because underneath they are all the same one door.
This is the extension model in a nutshell: a readable command maps to a connector, a function, and an order of arguments. The app defines the built-in ones; the vocabulary can grow to read like native Catena while every command remains, underneath, a plain and inspectable API call. The mappings a sequence uses are recorded with the sequence itself, so you can always list a sequence's commands and see what each one actually calls.
Currency is worth one extra note, because it also appears as unit arithmetic. Money is treated as a unit, so an expression like $100 + €20 needs a conversion rate to be meaningful — and a rate can only come from a rate provider through API. That is precisely what CURRENCY fetches. Mixing currencies with no rate available is reported as an error with a hint, never a silently wrong sum.
When a call cannot complete
An outside call can fail for reasons that have nothing to do with your sequence: the network is down, the rate source is unreachable, the provider returns an error. Catena handles this in a way that is unusual, and important to understand, because it shapes how you write and run sequences.
Catena never returns garbage, and never silently stops. When an API call cannot complete, the sequence does not crash and does not quietly substitute a wrong number. Instead it pauses and offers you a plain choice:
- Stop — end the sequence here.
- Try again — re-attempt the call, for instance once the network is back.
- Enter your own value — type the value in by hand, and the sequence carries on from that point.
That third option is what keeps an important sequence usable when one networked step is temporarily unavailable. A month-end calculation does not have to fail just because a rate feed is briefly offline; you can supply the rate yourself and continue.
There is no try/catch in Catena. The language has no exception-handling constructs at all. Error-handling scaffolding is exactly the kind of complexity Catena avoids. Recovery is not something you code around each call — it is a uniform behaviour of the calculator that applies to every outside call automatically. You write the API (or CURRENCY) call plainly; if it cannot complete, the Stop / Try again / Enter-a-value choice appears on its own.
Offline mode makes this proactive. An engine setting, OFFLINE, controls whether outside calls are even attempted. Set it, and the calculator skips the network entirely and goes straight to asking you for the value — useful when you know you are working offline and do not want to wait for a timeout on every call. Set to its automatic mode, the calculator makes that decision for you when it detects no connectivity. A sequence author can also pre-declare a default value or a default action, so the common case needs no prompt at all. Engine settings like this one travel with the sequence rather than living in app-wide options; see Engine settings for how to set OFFLINE and the others.
The currency connector shows the same spirit in its own handling: today's rates are cached until the next local midnight, and if you are offline the conversion uses the cached rate and marks it as stale on the tape rather than failing. Only when there is no usable rate at all does it fall back to asking you to retry, enter a rate, or stop. That behaviour, and the cache options behind it, are described in Connectors.
The worked example: currency, two ways
Suppose a price came in as US dollars and you need it in euro to continue a calculation. Here is a small sequence that asks for the dollar amount, converts it, and both shows and records the result.
Using the friendly CURRENCY command:
PROMPT "Price in USD?" AS Num -> priceUsd
CURRENCY ("USD", priceUsd, "EUR") -> itemPrice
OUT itemPriceLine by line: PROMPT asks you for the dollar figure and validates it as a number; CURRENCY converts from "USD" to "EUR" using the live rate source and stores the euro amount in itemPrice; OUT shows the euro result and records it on the paper tape in one step.
The identical conversion written as a raw API call:
PROMPT "Price in USD?" AS Num -> priceUsd
API ("currencyrates", "convert", "USD", priceUsd, "EUR") -> itemPrice
OUT itemPriceThese two sequences do the same thing, because CURRENCY is the API call in the second version — only more readable. You would normally write the first; the second is shown here only to make the connection concrete.
Now run it with the network unplugged. The conversion step cannot reach the rate source, so instead of failing the sequence offers you Stop, Try again, or Enter your own value. Choose Enter your own value, type the euro amount you want to use, and the sequence continues to the OUT line exactly as if the conversion had returned that number. Nothing was silently guessed, and nothing crashed — the one door out was closed for a moment, and you decided how to proceed.
See also
- Connectors — setting up the currency source, custom endpoints, and printers in Settings.
- Engine settings —
OFFLINEand the other settings that travel with a sequence. - Commands and the standard library —
PROMPT,OUT, and the built-in command vocabulary used in the examples above.