IGNITRAOpen the app

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).

  1. GET /v1/auth/nonce gives a single-use nonce, the accepted domains and chain_id 4663.
  2. Build the message with one of those domains, a URI on it, chain id 4663 and the nonce, and sign it (personal_sign).
  3. POST /v1/auth/verify with { "message", "signature", "return_token": true } returns a session token, igs_….
  4. 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:
400Malformed request: the message says which field.
401 / 403Not signed in, an expired session, or a cookie write from another site.
402plan_limit_reached with limit, plan and max: active flows, or a trigger or action your plan does not include.
404Not found, or not yours (the same answer on purpose).
409The request conflicts with the current state, e.g. rotating the hook of a flow that has none.
422invalid_flow (or invalid_values for templates) with issues: a list of { path, message }, e.g. an unknown field with a suggestion.
429Rate 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/triggersTrigger catalog: params schema and data fields of every trigger
GET/v1/actionsAction catalog: params schema and outputs of every action
GET/v1/templates?category=Template gallery (official and public community templates)
GET/v1/templates/:slugOne template: placeholders, definition and outline
POST/v1/hooks/:flow_id/:tokenInbound webhook: see Webhooks
POST/v1/integrations/vigil/:connection_idVIGIL alert deliveries, signed by VIGIL: see Integrations

Sign-in

GET/v1/auth/nonceA 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/logoutEnds the session

API keys

Managing keys needs a wallet session: a key can not create keys.

GET/v1/api-keysYour 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/:idRevokes a key at once

Account

GET/v1/meYou: address, plan and its limits, timezone
PATCH/v1/me{ timezone }: an IANA zone such as Asia/Jakarta
GET/v1/me/usageRuns this month against the plan
GET/v1/me/webhook-secretThe secret that signs your inline-URL webhooks
GET/v1/notificationsNotices 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/flowsYour 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/:idOne 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/:idDeletes the flow and its history
POST/v1/flows/:id/pauseStops matching new events
POST/v1/flows/:id/resumeRe-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/rotateA 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/:idThe 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/retryRuns 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/connectionsYour 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/resendSends a new email code
POST/v1/connections/google/startStarts Google sign-in for Sheets; returns the URL to open
DELETE/v1/connections/:idDeletes 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/grantsYour 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/:idOne 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/revokeStops it at once (the session key is deleted); answers the Safe transaction that also removes the role
GET/v1/onchain/reviewEvery 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-allEmergency 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/templatesTemplates you shared
DELETE/v1/templates/:slugUnshares one of your templates

Builder helpers

GET/v1/chain/addresses/:addressWhat 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/fieldsThe variables available to each step of a draft