Workflow Schema Reference
Details of the workflow JSON schema that the rest of the docs do not
spell out: which config fields are JSON-encoded strings rather than
raw arrays, how functionArgs maps onto ABI inputs, and the exact
shapes the strict validator accepts.
The critical fields
abi — must be a JSON string, not an array
Wrong (causes 422 error):
{
"abi": [{ "name": "transfer", "type": "function", ... }]
}Correct:
{
"abi": "[{\"name\":\"transfer\",\"type\":\"function\",...}]"
}Always JSON.stringify() your ABI before putting it in the config.
functionArgs — a JSON-stringified positional array
functionArgs is a JSON-stringified positional array whose elements
map to the ABI inputs by index.
Wrong:
{
"functionArgs": "[{\"to\":\"0xRecipient\",\"amount\":\"1000000\"}]"
}Correct:
{
"functionArgs": "[\"0xRecipient\",\"1000000\"]"
}Note: named-field objects only apply to a single tuple/struct
parameter, not as a wrapper for all args.
tokenConfig for web3/approve-token
tokenConfig accepts either a bare 0x-prefixed token address
(treated as the token address directly) or a JSON string with
the full custom token shape. Both work:
{ "tokenConfig": "0xTokenAddress" }{
"tokenConfig": "{\"mode\":\"custom\",\"customToken\":{\"address\":\"0xTokenAddress\"}}"
}Note: symbol is fetched on-chain; you don’t need to provide it.
Deadlines — keep template substitution result valid JSON
Template expressions inside functionArgs are supported and
resolve before JSON.parse. The rule is: ensure the substituted
result is valid JSON. Computing values like deadlines beforehand
is the safest approach:
const deadline = Math.floor(Date.now() / 1000) + 600;
functionArgs: JSON.stringify([deadline])Avoid arithmetic expressions after substitution
(e.g. {{timestamp}} + 3600) as they produce invalid JSON.
network — recommended as a string chain ID
A string chain ID is the recommended form:
{ "network": "11155111" }
{ "network": "1" }The API also accepts raw numbers and legacy names like
"sepolia" or "base" at runtime, but string chain IDs
are the safest and most explicit.
gasLimitMultiplier — pass as a string
{ "gasLimitMultiplier": "1.5" }Helps avoid out-of-gas errors on complex transactions.
Endpoint reference
Create workflow
POST /api/workflows/createExecute workflow
POST /api/workflow/{workflowId}/executeor
POST /api/workflows/{workflowId}/executeBoth routes work identically.
Get execution status
GET /api/workflows/executions/{executionId}/statusReturns a transactionHashes array in the success payload —
you can read tx hashes directly from the status response
without fetching logs separately. Each entry is a receipt object
(hash, nodeId, verified, …), not a bare hash string — see
Transaction Hashes.
Get execution logs
GET /api/workflows/executions/{executionId}/logsNode structure
Every node follows this shape (status and description are optional):
{
id: "unique-id",
type: "trigger" | "action",
data: {
label: "Human readable name",
type: "trigger" | "action",
config: { ... },
// status and description are optional
}
}Trigger node
{
id: "trigger",
type: "trigger",
data: {
label: "Manual Trigger",
type: "trigger",
config: { triggerType: "Manual" },
}
}Use the capitalized canonical value: "Manual", "Schedule",
"Webhook", "Event", "Block", "Transfer".
Edge structure
{
id: "e1",
source: "trigger",
target: "step-1"
}Finding your wallet integration ID
const res = await axios.get("https://app.keeperhub.com/api/integrations", {
headers: { Authorization: `Bearer ${API_KEY}` }
});
const walletId = res.data[0].id; // bare array, no wrapperTransaction hash location
The workflow execution status endpoint returns transactionHashes
directly in the success payload. You can read tx hashes from
the status response without fetching logs. Each entry is a receipt
object, so pull .hash out of it rather than using the entry itself
as the hash:
const status = await getExecutionStatus(executionId);
const txHashes = status.transactionHashes.map((entry) => entry.hash);What we found genuinely undocumented
abimust beJSON.stringify()’d — causes a silent 422 if notgasLimitMultipliermust be a string, not a number- The edge shape (
id,source,target) is not in the quickstart - The create/status/logs endpoint paths are not in one place in the docs