API
Everything the app does goes through this API, so scripts can do it too. Requests and responses are JSON. The web app serves the API at https://ignitra.run/api: every path below goes after that, e.g. https://ignitra.run/api/v1/flows.
Sign in
Two ways in, both sent as Authorization: Bearer …. Scripts use an API key (igk_…, Team plan): create one under Settings and see SDK & API keys. On any plan, a wallet session works too: you sign a Sign-In with Ethereum (EIP-4361) message. Signing is free, sends no transaction and needs no funds. Smart-contract wallets work too (ERC-1271).
GET /v1/auth/nonce gives a single-use nonce, the accepted domains and chain_id 4663.- Build the message with one of those domains, a URI on it, chain id 4663 and the nonce, and sign it (personal_sign).
POST /v1/auth/verify with { "message", "signature", "return_token": true } returns a session token, igs_….- Send it as
Authorization: Bearer igs_…. Sessions expire (see expires_at); sign in again then.
// Node 20+, with viem (npm i viem). Sign in with the wallet that owns your flows:
// the key stays on your machine, IGNITRA only ever receives the signature.
import { privateKeyToAccount } from "viem/accounts";
import { createSiweMessage } from "viem/siwe";
const API = "https://ignitra.run/api";
const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY); // or any viem wallet (hardware, WalletConnect…)
const { nonce, domains, chain_id } = await (await fetch(`${API}/v1/auth/nonce`)).json();
const message = createSiweMessage({
address: account.address,
chainId: chain_id, // 4663, Robinhood Chain
domain: domains[0],
uri: "https://ignitra.run",
nonce,
version: "1",
issuedAt: new Date(),
statement: "Sign in to IGNITRA",
});
const signature = await account.signMessage({ message });
const res = await fetch(`${API}/v1/auth/verify`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ message, signature, return_token: true }),
});
const { token, expires_at } = await res.json();
// Every other call: Authorization: Bearer <token>
const flows = await (await fetch(`${API}/v1/flows`, { headers: { authorization: `Bearer ${token}` } })).json();
console.log(expires_at, flows.data.map((f) => `${f.name}: ${f.status}`));
Browsers get an HttpOnly ignitra_session cookie instead of a token. Cookie-authenticated writes must come from the IGNITRA site itself (CSRF protection). Treat a bearer token like a password: anyone who has it acts as you until it expires or you log out.
Conventions
- Ids are prefixed:
fl_ flows, run_ runs, conn_ connections, usr_ users, key_ API keys, grt_ onchain grants, oca_ onchain actions. Times are ISO-8601 UTC; addresses are lowercase. - Results come wrapped in
data (the sign-in endpoints answer flat). Runs page backwards: pass the response's next_before as before until it is null. - Errors are
{ "error": { "code", "message", … } } with a meaningful status:
| 400 | Malformed request: the message says which field. |
| 401 / 403 | Not signed in, an expired session, or a cookie write from another site. |
| 402 | plan_limit_reached with limit, plan and max: active flows, or a trigger or action your plan does not include. |
| 404 | Not found, or not yours (the same answer on purpose). |
| 409 | The request conflicts with the current state, e.g. rotating the hook of a flow that has none. |
| 422 | invalid_flow (or invalid_values for templates) with issues: a list of { path, message }, e.g. an unknown field with a suggestion. |
| 429 | Rate limited: wait for Retry-After seconds. Every answer carries RateLimit-Limit, -Remaining and -Reset (limits). |
Example: create a flow
curl -X POST "https://ignitra.run/api/v1/flows" \
-H "Authorization: Bearer $IGNITRA_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Big USDG transfers",
"activate": true,
"definition": {
"trigger": { "type": "TOKEN_TRANSFER", "params": { "token": "0x5fc5360d0400a0fd4f2af552add042d716f1d168" } },
"conditions": { "all": [{ "field": "amount_decimal", "op": ">=", "value": "10000" }] },
"actions": [{ "step_id": "notify", "type": "WEB_PUSH", "params": { "title": "Big transfer", "body": "{{trigger.amount_formatted}} {{trigger.token.symbol}}" } }]
}
}'
Easier: start from a template with POST /v1/templates/:slug/instantiate, which only needs the placeholder values.
Endpoints
Public (no sign-in)
| GET | /v1/triggers | Trigger catalog: params schema and data fields of every trigger |
| GET | /v1/actions | Action catalog: params schema and outputs of every action |
| GET | /v1/templates?category= | Template gallery (official and public community templates) |
| GET | /v1/templates/:slug | One template: placeholders, definition and outline |
| POST | /v1/hooks/:flow_id/:token | Inbound webhook: see Webhooks |
| POST | /v1/integrations/vigil/:connection_id | VIGIL alert deliveries, signed by VIGIL: see Integrations |
Sign-in
| GET | /v1/auth/nonce | A single-use nonce (10 minutes), the accepted domains and the chain id |
| POST | /v1/auth/verify | { message, signature, return_token? }: starts a session |
| POST | /v1/auth/logout | Ends the session |
API keys
Managing keys needs a wallet session: a key can not create keys.
| GET | /v1/api-keys | Your keys: name, prefix, rate, last use (never the key) |
| POST | /v1/api-keys | { name }: a new key (Team plan), returned once as key |
| DELETE | /v1/api-keys/:id | Revokes a key at once |
Account
| GET | /v1/me | You: address, plan and its limits, timezone |
| PATCH | /v1/me | { timezone }: an IANA zone such as Asia/Jakarta |
| GET | /v1/me/usage | Runs this month against the plan |
| GET | /v1/me/webhook-secret | The secret that signs your inline-URL webhooks |
| GET | /v1/notifications | Notices such as auto-pauses |
| GET | /v1/audit-log?limit=&before= | Every change to the account, newest first: who (your wallet, which API key, the operator or IGNITRA itself), what, and from which address |
Flows
| GET | /v1/flows | Your flows with status and stats |
| POST | /v1/flows/validate | { definition }: validates without saving; returns the normalized definition |
| POST | /v1/flows | { name, definition, activate? }: creates a flow (a draft unless activate is true) |
| GET | /v1/flows/:id | One flow with its current definition (and hook_url for inbound webhooks) |
| PATCH | /v1/flows/:id | { name?, definition? }: a new definition becomes a new version |
| DELETE | /v1/flows/:id | Deletes the flow and its history |
| POST | /v1/flows/:id/pause | Stops matching new events |
| POST | /v1/flows/:id/resume | Re-validates and activates (the plan's active-flow limit applies) |
| POST | /v1/flows/:id/test | { sample? }: one TEST run with the latest real event (or your sample) |
| POST | /v1/flows/:id/hook/rotate | A new inbound webhook URL; the old one stops working |
Runs
| GET | /v1/flows/:id/runs?limit=&before=&status= | Newest first, up to 200 per page; status is one of queued, running, succeeded, partial, failed, skipped |
| GET | /v1/runs/:id | The full trace: trigger evidence, every condition, every step attempt |
| GET | /v1/dead-letters?limit=&before= | Runs whose steps failed for good (a permanent error, or every retry used up), newest first |
| POST | /v1/runs/:id/retry | Runs a failed or partial run's failed steps again, with the same idempotency keys: delivered steps are not repeated. The flow must be active |
| GET | /v1/flows/:id/evaluations?limit=&outcome= | “Why it did not run”: conditions_failed, rate_limited, plan_limited, undetermined, missed |
Connections
Secrets go in and never come out: responses only carry labels and safe metadata.
| GET | /v1/connections | Your connections |
| POST | /v1/connections | { type: "telegram" | "webhook" | "email" | "http" | "vigil" | "mandate", … }: webhook, signed http and MANDATE connections return their signing_secret once; VIGIL ones their receiver_url |
| POST | /v1/connections/:id/verify | { code }: confirms an email address; { webhook_secret }: the secret VIGIL showed for a receiver you registered yourself |
| POST | /v1/connections/:id/resend | Sends a new email code |
| POST | /v1/connections/google/start | Starts Google sign-in for Sheets; returns the URL to open |
| DELETE | /v1/connections/:id | Deletes a connection; refused (409 connection_in_use) while a flow sends through it or its trigger listens on it (flows), or while a pending or active grant must pass it (grants) |
Onchain grants
Team plan. Creating and verifying a grant needs a wallet session; listing and revoking also work with an API key. See Onchain.
| GET | /v1/onchain/grants | Your grants with their scope and status |
| POST | /v1/onchain/grants | { label, safe, targets, max_value_per_tx, max_value_per_day, expires_at, mandate_connection_id? }: a pending grant and the Safe transaction that approves it |
| GET | /v1/onchain/grants/:id | One grant: the session key's gas balance, the Safe transaction while pending, recent actions |
| POST | /v1/onchain/grants/:id/verify | { tx_hash }: proves the mined Safe transaction grants exactly this; the grant becomes active |
| POST | /v1/onchain/grants/:id/revoke | Stops it at once (the session key is deleted); answers the Safe transaction that also removes the role |
| GET | /v1/onchain/review | Every grant that can still act: what it allows, the flows using it, its last 7 days, and findings (unused, stale, no MANDATE, expiring) |
| POST | /v1/onchain/grants/revoke-all | Emergency stop: revokes every live grant at once (session keys deleted); answers one optional Safe transaction per Safe that removes the roles |
| GET | /v1/onchain/actions?grant_id=&limit= | Every onchain action with its status, MANDATE decision and transaction hash |
Templates
| POST | /v1/templates/:slug/instantiate | { values, name?, activate? }: creates a flow from a template |
| POST | /v1/templates | { flow_id, name, summary, description?, category, tags?, visibility? }: shares one of your flows as a template |
| GET | /v1/me/templates | Templates you shared |
| DELETE | /v1/templates/:slug | Unshares one of your templates |
Builder helpers
| GET | /v1/chain/addresses/:address | What an address is: token (symbol, decimals), contract (verified ABI) or wallet |
| POST | /v1/samples | { trigger, conditions? }: the latest real event for a trigger, and how the conditions treat it |
| POST | /v1/catalog/fields | The variables available to each step of a draft |