Tamiasdocs

Run a workflow from code

Check a condition in your own code, ask Tamias for the run, and send it from a wallet your code holds.

What it does

Your code decides when a workflow runs: every Friday, when a balance passes a line, when a health factor drops. Tamias builds the run, checks it, and hands back one transaction. Your code sends it from its own wallet.

No signer has to be around, and the Safe's signers and threshold don't change. The run can do only what the workflow's automation allows, the same limits a signer has.

Tamias never sends the run and never holds your code's wallet.

What you need

  1. An automated workflow. On the workflow's page, turn on Automate this workflow. The owners sign that once.
  2. A run key. A wallet your code holds, with some of the chain's native token for gas. Add its address under WHO → Add a run key before turning automation on, or with Change run keys after. See run keys.
  3. An API key for the Safe, from Settings → API keys. See API keys.
  4. The workflow's id. It is the last part of the workflow's address in the app: app.tamias.io/safe/0x…/workflows/<workflow id>.

The API key tells Tamias who is asking. The run key is who the chain lets run the workflow. Keep them apart: the API key never signs anything, and the run key never talks to Tamias.

A complete example

This runs a workflow on Arbitrum whenever the Safe holds more than 1,000 USDC. It uses viem and Node 22 or later. Run it on a schedule, e.g. cron every hour.

import { createPublicClient, createWalletClient, erc20Abi, http, parseUnits } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { arbitrum } from "viem/chains";

const API = "https://api.tamias.io";
const WORKFLOW_ID = "624f5463-8be2-44c4-9c1b-942d10a778d8";
const SAFE = "0x901a80df13c9d414a5db23cfe2c5597830314ad1";
const USDC = "0xaf88d065e77c8cc2239327c5edb3a432268e5831";

const headers = {
  Authorization: `Bearer ${process.env.TAMIAS_API_KEY}`,
  "Content-Type": "application/json",
};
const runKey = privateKeyToAccount(process.env.RUN_KEY_PRIVATE_KEY as `0x${string}`);
const transport = http(process.env.ARBITRUM_RPC_URL);
const chain = createPublicClient({ chain: arbitrum, transport });
const wallet = createWalletClient({ account: runKey, chain: arbitrum, transport });

// 1. Your condition. Anything you can compute can go here.
const balance = await chain.readContract({ address: USDC, abi: erc20Abi, functionName: "balanceOf", args: [SAFE] });
if (balance < parseUnits("1000", 6)) {
  console.log("below the line, nothing to do");
  process.exit(0);
}

// 2. Ask Tamias for the run, as the run key will send it.
const res = await fetch(`${API}/v1/workflows/${WORKFLOW_ID}/run`, {
  method: "POST",
  headers,
  body: JSON.stringify({ from: runKey.address }),
});
const run = await res.json();
if (!res.ok) throw new Error(`Tamias refused the run: ${run.error}`);
if (run.chainId !== arbitrum.id) throw new Error(`this workflow is on chain ${run.chainId}`);

// 3. Send exactly that transaction from the run key. It pays the gas.
const hash = await wallet.sendTransaction({
  to: run.transaction.to,
  data: run.transaction.data,
  value: BigInt(run.transaction.value),
});
const receipt = await chain.waitForTransactionReceipt({ hash });
console.log(`run ${receipt.status}: ${hash}`);

// 4. Tell Tamias, so the run shows under the workflow and in the activity log.
await fetch(`${API}/v1/workflows/${WORKFLOW_ID}/runs`, {
  method: "POST",
  headers,
  body: JSON.stringify({ txHash: hash }),
});

Keep TAMIAS_API_KEY and RUN_KEY_PRIVATE_KEY in your secret store or environment, never in the code.

Asking for the run

POST https://api.tamias.io/v1/workflows/<workflow id>/run
Authorization: Bearer tamias_…
Content-Type: application/json

{ "from": "<the run key's address>" }

Tamias builds the run the way Run does in the app. It gets fresh quotes and uses the amounts the workflow was recorded with. Then it:

  1. simulates the run
  2. adds the same balance checks a run proposed to the Safe carries. Each one makes the whole run revert if the Safe would end up with less than the simulation said, beyond the allowed slippage (0.5% unless the recording set another). An amount that is fixed in the calldata, such as a plain transfer, needs no check.
  3. dry-runs the finished transaction from your run key, through the Safe's Roles modifier

What comes back:

{
  "workflowId": "624f5463-…",
  "chainId": 42161,
  "transaction": { "from": "0x5c41…", "to": "0x…roles modifier", "data": "0x…", "value": "0" },
  "calls": [ { "to": "0x…", "value": "0", "data": "0x…" } ],
  "checks": [ { "label": "Refuse unless USDC spent is at most 0.5% above what was simulated", "to": "0x…", "data": "0x…" } ]
}

Send transaction exactly as it is, from from. calls and checks are there for your logs: what the run does, and what it refuses.

Ask again for every run. A run is built from fresh quotes and today's balances, so an old one may no longer pass its checks.

Reporting the run

POST https://api.tamias.io/v1/workflows/<workflow id>/runs
Authorization: Bearer tamias_…
Content-Type: application/json

{ "txHash": "0x…" }

Tamias reads the transaction from the chain and records it only if it is mined, went through this Safe's Roles modifier, ran this workflow, and came from a wallet the permission names. Nothing in your request is taken on trust.

The run then lists under the workflow's runs as "by run key 0x… · via API key “name”". The activity log says "the API key “name” ran the workflow … from run key 0x…". Reporting the same hash twice records it once (201 the first time, 200 after).

Reporting is optional: the run happened either way. Without it, Tamias just doesn't list it.

When it is refused

Asking for a run answers 422 with the reason in error when the chain would refuse the run. Nothing is sent, and no gas is spent.

error saysWhat to do
this workflow is not automated …Turn on Automate this workflow and add the run key.
this wallet is not one of the signers or run keys this workflow was allowed forfrom is not a run key for this workflow. Check the address, or add it with Change run keys.
this run would spend more … than is left of what the permission allows a week, or this workflow already ran as often as the permission allows a weekThis period's limit is used. It refills on the date the workflow page shows.
the run would fail on chain: …The simulation reverted, e.g. the Safe doesn't hold what the workflow spends.
the workflow was allowed, but one of its calls failed when it ran …A step reverted inside the Safe. It would fail with every signature too.
the contract at 0x… was upgraded since this workflow was allowed …A protocol it calls changed its code. Check what changed, then allow the workflow again.
this permission expired on …Turn automation on again.
the chain holds an older version of this workflow's permission …Press Update it on the workflow page and have the owners sign.

Other answers:

StatusWhy
401 invalid API keyThe key is wrong or revoked, or the Safe left the organization.
401 this route takes an API key …No Authorization: Bearer tamias_… header.
402 subscription requiredThe organization's plan has lapsed. Running is paused until it is paid.
404 not foundNo such workflow on this key's Safe.
429Too many requests from this key. Wait a minute.
503The run couldn't be simulated just now. Try again.

What the run key can and can't do

It can run this workflow:

  • to the contracts it calls, with the arguments that were recorded
  • with the money landing in the Safe
  • up to the amount and number of runs per period the owners chose

It can't:

  • sign or propose anything for the Safe
  • run a different workflow
  • send funds anywhere but where the workflow sends them
  • run more often than the limit allows

If it is stolen, the most it can do is run this workflow early, within this period's limit. Remove it with Change run keys. The owners sign, and from then on the chain refuses it.

The balance checks are added by Tamias. A run key holder who builds a run by hand, without asking Tamias, can leave them out. They still can't move more than the limit, or anywhere else.

Running on a schedule without writing code

Not yet. Tamias doesn't run workflows on its own schedule or trigger. Your code decides when, and sends the run.

On this page