AutomationFlowsData & Sheets › History List API

History List API

History List API. Uses postgres. Webhook trigger; 4 nodes.

Webhook trigger★★★★☆ complexity4 nodesPostgres
Data & Sheets Trigger: Webhook Nodes: 4 Complexity: ★★★★☆ Added:

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 →

Download .json
{
  "name": "History List API",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "GET",
        "path": "api/v1/history",
        "responseMode": "lastNode",
        "options": {}
      },
      "id": "88888888-8888-4888-8888-000000000001",
      "name": "GET /api/v1/history",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        120,
        400
      ]
    },
    {
      "parameters": {
        "jsCode": "const query = $json.query ?? {};\nconst supportedFlowTypes = new Set(['review', 'compliance', 'document', 'tests', 'batch', 'pull_request_review', 'pull_request_tests']);\nconst supportedStatuses = new Set(['pending', 'success', 'failed']);\nfunction optionalText(value, field) { if (value === undefined || value === null) return null; const text = String(value).trim(); if (!text) throw new Error(`Query invalida para historico: ${field} nao pode ser vazio.`); return text; }\nfunction optionalDate(value, field) { const text = optionalText(value, field); if (text === null) return null; const timestamp = Date.parse(text); if (Number.isNaN(timestamp)) throw new Error(`Query invalida para historico: ${field} deve ser uma data valida.`); return new Date(timestamp).toISOString(); }\nfunction optionalBoolean(value) { if (value === undefined || value === null) return null; if (value === true || value === 'true') return true; if (value === false || value === 'false') return false; throw new Error('Query invalida para historico: cache_hit deve ser true ou false.'); }\nconst rawLimit = query.limit === undefined ? 20 : Number(query.limit);\nif (!Number.isInteger(rawLimit) || rawLimit < 1 || rawLimit > 100) throw new Error('Query invalida para historico: limit deve ser um inteiro entre 1 e 100.');\nconst cursor = optionalText(query.cursor, 'cursor');\nconst flowType = optionalText(query.flow_type, 'flow_type');\nconst status = optionalText(query.status, 'status');\nconst model = optionalText(query.model, 'model');\nconst from = optionalDate(query.from, 'from');\nconst to = optionalDate(query.to, 'to');\nconst cacheHit = optionalBoolean(query.cache_hit);\nif (flowType !== null && !supportedFlowTypes.has(flowType)) throw new Error('Query invalida para historico: flow_type nao suportado.');\nif (status !== null && !supportedStatuses.has(status)) throw new Error('Query invalida para historico: status nao suportado.');\nreturn [{ json: { limit: rawLimit, query_parameters: [rawLimit + 1, cursor, flowType, status, model, from, to, cacheHit] } }];"
      },
      "id": "88888888-8888-4888-8888-000000000002",
      "name": "Validate History List Query",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        360,
        400
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "WITH cursor_execution AS (\n  SELECT id, created_at\n  FROM executions\n  WHERE id::text = $2::text\n), selected_executions AS (\n  SELECT e.id,\n         e.created_at,\n         e.flow_type,\n         e.status,\n         e.duration_ms,\n         e.cache_hit,\n         e.source_execution_id,\n         t.execution_id AS telemetry_execution_id,\n         t.provider,\n         t.model_requested,\n         t.model_used,\n         t.prompt_tokens,\n         t.completion_tokens,\n         t.total_tokens,\n         t.cost_usd,\n         t.input_cost_usd,\n         t.output_cost_usd,\n         t.cache_read_tokens\n  FROM executions e\n  LEFT JOIN execution_telemetry t ON t.execution_id = e.id\n  WHERE ($2::text IS NULL OR EXISTS (\n           SELECT 1\n           FROM cursor_execution cursor_row\n           WHERE e.created_at < cursor_row.created_at\n              OR (e.created_at = cursor_row.created_at AND e.id::text < cursor_row.id::text)\n        ))\n    AND ($3::text IS NULL OR e.flow_type = $3::text)\n    AND ($4::text IS NULL OR e.status = $4::text)\n    AND ($5::text IS NULL OR t.model_requested = $5::text)\n    AND ($6::timestamptz IS NULL OR e.created_at >= $6::timestamptz)\n    AND ($7::timestamptz IS NULL OR e.created_at <= $7::timestamptz)\n    AND ($8::boolean IS NULL OR e.cache_hit = $8::boolean)\n  ORDER BY e.created_at DESC, e.id DESC\n  LIMIT $1::integer\n), records AS (\n  SELECT e.id,\n         e.created_at,\n         jsonb_build_object(\n           'id', e.id::text,\n           'type', e.flow_type,\n           'status', e.status,\n           'timestamp', e.created_at,\n           'duration_ms', e.duration_ms,\n           'cache_hit', e.cache_hit,\n           'source_execution_id', e.source_execution_id::text,\n           'telemetry', CASE WHEN e.telemetry_execution_id IS NULL THEN NULL ELSE jsonb_build_object(\n             'provider', e.provider,\n             'model_requested', e.model_requested,\n             'model_used', e.model_used,\n             'openrouter_generation_id', NULL,\n             'prompt_tokens', e.prompt_tokens,\n             'completion_tokens', e.completion_tokens,\n             'total_tokens', e.total_tokens,\n             'cost_total_usd', e.cost_usd,\n             'cost_input_usd', e.input_cost_usd,\n             'cost_output_usd', e.output_cost_usd,\n             'cache_read_tokens', e.cache_read_tokens\n           ) END,\n           'steps', COALESCE((\n             SELECT jsonb_agg(jsonb_build_object(\n               'node_name', step.node_name,\n               'kind', step.kind,\n               'status', step.status,\n               'duration_ms', step.duration_ms\n             ) ORDER BY step.created_at ASC)\n             FROM execution_steps step\n             WHERE step.execution_id = e.id\n           ), '[]'::jsonb)\n         ) AS item\n  FROM selected_executions e\n)\nSELECT COALESCE(jsonb_agg(item ORDER BY created_at DESC, id DESC), '[]'::jsonb) AS items\nFROM records;",
        "options": {
          "queryReplacement": "={{ $json.query_parameters }}"
        }
      },
      "id": "88888888-8888-4888-8888-000000000003",
      "name": "Fetch History List",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        620,
        400
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const request = $('Validate History List Query').first().json;\nconst rows = Array.isArray($json.items) ? $json.items : [];\nconst formatter = new Intl.DateTimeFormat('en-CA', { timeZone: 'America/Sao_Paulo', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h23', timeZoneName: 'longOffset' });\nfunction toSaoPauloIso(value) { const date = new Date(value); if (Number.isNaN(date.getTime())) return String(value); const parts = Object.fromEntries(formatter.formatToParts(date).map((part) => [part.type, part.value])); return `${parts.year}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}:${parts.second}${parts.timeZoneName.replace('GMT', '')}`; }\nconst items = rows.slice(0, request.limit).map((item) => ({ ...item, timestamp: toSaoPauloIso(item.timestamp) }));\nreturn [{ json: { items, page: { limit: request.limit, next_cursor: rows.length > request.limit && items.length > 0 ? items[items.length - 1].id : null } } }];"
      },
      "id": "88888888-8888-4888-8888-000000000004",
      "name": "Respond History List",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        400
      ]
    }
  ],
  "connections": {
    "GET /api/v1/history": {
      "main": [
        [
          {
            "node": "Validate History List Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate History List Query": {
      "main": [
        [
          {
            "node": "Fetch History List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch History List": {
      "main": [
        [
          {
            "node": "Respond History List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [],
  "triggerCount": 0,
  "updatedAt": "2026-06-01T00:00:00.000Z",
  "versionId": "88888888-8888-4888-8888-000000000005"
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

History List API. Uses postgres. Webhook trigger; 4 nodes.

Source: https://github.com/Cledson96/case-n8n/blob/1b1f239363ff1975c6ec29194e55dc762eaa7deb/workflows/history-list.json — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

CMM. Uses httpRequest, postgres, redis. Webhook trigger; 90 nodes.

HTTP Request, Postgres, Redis
Data & Sheets

Scraping. Uses httpRequest, postgres, @apify/n8n-nodes-apify, respondToWebhook. Webhook trigger; 61 nodes.

HTTP Request, Postgres, @Apify/N8N Nodes Apify
Data & Sheets

Projects. Uses postgres. Webhook trigger; 58 nodes.

Postgres
Data & Sheets

Workflow B — AI Listing Engine. Uses httpRequest, postgres, errorTrigger. Webhook trigger; 47 nodes.

HTTP Request, Postgres, Error Trigger
Data & Sheets

How it works

Postgres, Email Send