This workflow follows the Execute Workflow Trigger → Postgres recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "[FC Sub] Check Credit Spend",
"settings": {
"executionOrder": "v1"
},
"nodes": [
{
"parameters": {
"content": "## [FC Sub] Check Credit Spend\n**Purpose:** Aggregates the agent's Firecrawl credit burn for the current session. The agent calls this every few tool calls and before any expensive operation (crawl, batch_scrape, extract) to stay under its budget.\n\n**Called by:** main agent's `check_credits` tool.\n\n**Inputs:**\n- `session_id?` \u2014 defaults to the current execution id if omitted\n- `credit_cap?` \u2014 soft budget (default 500); the response includes `remaining_budget` computed from this\n\n**Returns:**\n- `session_id`\n- `spent_this_session` \u2014 total credits burned\n- `credit_cap` \u2014 the cap being compared against\n- `remaining_budget` \u2014 `credit_cap - spent_this_session` (can be negative if over)\n- `over_budget` \u2014 boolean\n- `by_operation` \u2014 map of operation name \u2192 credits spent\n- `recent_calls` \u2014 last 10 calls with url + credits + status\n\n**No Firecrawl API calls \u2014 pure Postgres read.**\n\n**Credentials:** Postgres RW (with SELECT on `firecrawl_credit_ledger`).",
"height": 560,
"width": 620,
"color": 6
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-40,
-600
],
"id": "sticky-fc-credits",
"name": "README"
},
{
"parameters": {
"inputSource": "passthrough"
},
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1.1,
"position": [
0,
0
],
"id": "fc-cr-trigger",
"name": "When Executed by Another Workflow"
},
{
"parameters": {
"jsCode": "const input = $input.first().json;\nconst q = (input.query && typeof input.query === 'object') ? input.query : {};\nconst pick = (k, def) => {\n if (input[k] !== undefined) return input[k];\n if (q[k] !== undefined) return q[k];\n return def;\n};\n\nconst session_id = (pick('session_id') || $execution.id || 'no-session').toString();\nlet credit_cap = Number(pick('credit_cap', 500));\nif (!Number.isFinite(credit_cap) || credit_cap < 0) credit_cap = 500;\n\nreturn [{ json: { session_id, credit_cap } }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
220,
0
],
"id": "fc-cr-prep",
"name": "Prep Input"
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT\n COALESCE(SUM(credits_used), 0)::int AS total_spent,\n COUNT(*)::int AS call_count,\n COALESCE(jsonb_object_agg(op_totals.operation, op_totals.credits)\n FILTER (WHERE op_totals.operation IS NOT NULL), '{}'::jsonb) AS by_operation\nFROM public.firecrawl_credit_ledger l\nLEFT JOIN LATERAL (\n SELECT operation, SUM(credits_used)::int AS credits\n FROM public.firecrawl_credit_ledger\n WHERE session_id = $1\n GROUP BY operation\n) op_totals ON true\nWHERE l.session_id = $1;",
"options": {
"queryReplacement": "={{ $json.session_id }}"
}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
440,
0
],
"id": "fc-cr-aggregate",
"name": "Aggregate Spend"
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT operation, credits_used, url, status, created_at\nFROM public.firecrawl_credit_ledger\nWHERE session_id = $1\nORDER BY created_at DESC\nLIMIT 10;",
"options": {
"queryReplacement": "={{ $('Prep Input').first().json.session_id }}"
}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
660,
0
],
"id": "fc-cr-recent",
"name": "Recent Calls"
},
{
"parameters": {
"jsCode": "const prep = $('Prep Input').first().json;\nconst agg = $('Aggregate Spend').first().json;\nconst recent = $input.all().map(i => i.json);\n\nconst total_spent = Number(agg.total_spent || 0);\nconst call_count = Number(agg.call_count || 0);\nconst by_operation = agg.by_operation || {};\nconst remaining = prep.credit_cap - total_spent;\nconst over_budget = remaining < 0;\nconst near_cap = remaining < prep.credit_cap * 0.2; // <20% remaining\n\nlet advice;\nif (over_budget) {\n advice = 'STOP. Session is over the credit cap. Summarize with what you have and inform the user.';\n} else if (near_cap) {\n advice = `WARNING. ${remaining} credits left (<20% of cap). Avoid crawl/batch/extract. Finish up with existing scraped content.`;\n} else if (total_spent === 0) {\n advice = 'Fresh session \u2014 no credits spent yet.';\n} else {\n advice = `${remaining} credits remaining. OK to continue.`;\n}\n\nreturn [{ json: {\n session_id: prep.session_id,\n spent_this_session: total_spent,\n credit_cap: prep.credit_cap,\n remaining_budget: remaining,\n over_budget,\n near_cap,\n call_count,\n by_operation,\n recent_calls: recent,\n advice\n} }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
880,
0
],
"id": "fc-cr-assemble",
"name": "Assemble Response"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "cr1",
"name": "session_id",
"value": "={{ $json.session_id }}",
"type": "string"
},
{
"id": "cr2",
"name": "spent_this_session",
"value": "={{ $json.spent_this_session }}",
"type": "number"
},
{
"id": "cr3",
"name": "credit_cap",
"value": "={{ $json.credit_cap }}",
"type": "number"
},
{
"id": "cr4",
"name": "remaining_budget",
"value": "={{ $json.remaining_budget }}",
"type": "number"
},
{
"id": "cr5",
"name": "over_budget",
"value": "={{ $json.over_budget }}",
"type": "boolean"
},
{
"id": "cr6",
"name": "near_cap",
"value": "={{ $json.near_cap }}",
"type": "boolean"
},
{
"id": "cr7",
"name": "call_count",
"value": "={{ $json.call_count }}",
"type": "number"
},
{
"id": "cr8",
"name": "by_operation",
"value": "={{ $json.by_operation }}",
"type": "object"
},
{
"id": "cr9",
"name": "recent_calls",
"value": "={{ $json.recent_calls }}",
"type": "array"
},
{
"id": "cr10",
"name": "advice",
"value": "={{ $json.advice }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1100,
0
],
"id": "fc-cr-return",
"name": "Return Credits"
}
],
"connections": {
"When Executed by Another Workflow": {
"main": [
[
{
"node": "Prep Input",
"type": "main",
"index": 0
}
]
]
},
"Prep Input": {
"main": [
[
{
"node": "Aggregate Spend",
"type": "main",
"index": 0
}
]
]
},
"Aggregate Spend": {
"main": [
[
{
"node": "Recent Calls",
"type": "main",
"index": 0
}
]
]
},
"Recent Calls": {
"main": [
[
{
"node": "Assemble Response",
"type": "main",
"index": 0
}
]
]
},
"Assemble Response": {
"main": [
[
{
"node": "Return Credits",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
[FC Sub] Check Credit Spend. Uses executeWorkflowTrigger, postgres. Event-driven trigger; 7 nodes.
Source: https://github.com/MinaSaad1/n8n-firecrawl-web-crawler-agent/blob/main/workflows/11-sub-credit-ledger.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
W4.1 - ROUTER (State + Voice). Uses executeWorkflowTrigger, postgres, redis. Event-driven trigger; 30 nodes.
SHEETS RAG. Uses googleDriveTrigger, postgres, googleSheets, executeWorkflowTrigger. Event-driven trigger; 24 nodes.
Gmail-Calendar. Uses executeWorkflowTrigger, postgres, googleCalendar, httpRequest. Event-driven trigger; 12 nodes.
Gmail-Triage. Uses executeWorkflowTrigger, httpRequest, postgres. Event-driven trigger; 10 nodes.
Gmail-Governance. Uses executeWorkflowTrigger, gmail, postgres. Event-driven trigger; 10 nodes.