AutomationFlowsData & Sheets › Process Multiple Requests in Fifo Using Openai Batch API and Supabase/postgres

Process Multiple Requests in Fifo Using Openai Batch API and Supabase/postgres

ByTeng Wei Herr @twh on n8n.io

You provide a list of prompts and a system instruction, the workflow batches them into a single OpenAI Batch API request. The batch job is tracked in a Supabase openai_batches table. A cron job polls OpenAI every 5 minutes, and once the batch completes, the results are decoded…

Event trigger★★★★☆ complexity19 nodesHTTP RequestSupabasePostgres
Data & Sheets Trigger: Event Nodes: 19 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #13238 — we link there as the canonical source.

This workflow follows the HTTP Request → 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 →

Download .json
{
  "nodes": [
    {
      "id": "9d2458b4-fea4-4a3d-a1a9-c9a6d2849560",
      "name": "Call Files API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -752,
        -160
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/files",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "multipart-form-data",
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "purpose",
              "value": "batch"
            },
            {
              "name": "file",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "data"
            }
          ]
        },
        "nodeCredentialType": "openAiApi"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2,
      "alwaysOutputData": true
    },
    {
      "id": "974a2186-4480-4cc2-bd82-391a9a0ceca7",
      "name": "Call Batch API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -528,
        -160
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/batches",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"input_file_id\": \"{{ $json.id }}\",\n  \"endpoint\": \"/v1/responses\",\n  \"completion_window\": \"24h\"\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2,
      "alwaysOutputData": true
    },
    {
      "id": "208f7358-6098-4137-b067-07238efc9d08",
      "name": "Start (mock data)",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -1200,
        -160
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "a93d70c7-923d-4219-9df4-00fde597a542",
      "name": "If status = completed",
      "type": "n8n-nodes-base.if",
      "position": [
        -528,
        160
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e9c51ad5-8ae7-46fc-b072-102fb37d6c14",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.status }}",
              "rightValue": "completed"
            },
            {
              "id": "916818b2-446e-4e61-9524-5f09fc094c40",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.output_file_id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "09f854d5-43bd-47b0-825b-220eff998eaa",
      "name": "Convert to batch requests in .jsonl",
      "type": "n8n-nodes-base.code",
      "position": [
        -976,
        -160
      ],
      "parameters": {
        "jsCode": "const inputs = $input.first().json.inputs\nconst systemPrompt = $input.first().json.systemPrompt;\n\nfunction createBatchRequestLine(input, index) {\n  return {\n    custom_id: `${index}_${Date.now()}`,\n    method: \"POST\",\n    url: \"/v1/responses\",\n    body: {\n      model: \"gpt-5-mini\",\n      instructions: systemPrompt,\n      input,\n    }\n  };\n}\n\nconst jsonlContent = inputs\n  .map((input, index) => JSON.stringify(createBatchRequestLine(input, index)))\n  .join('\\n');\n\n// Convert to base64 for binary\nconst base64Content = Buffer.from(jsonlContent, 'utf-8').toString('base64');\n\n// Return with binary data - ready for HTTP Request!\nreturn [{\n  binary: {\n    data: {\n      data: base64Content,\n      mimeType: 'application/x-ndjson',\n      fileName: 'batch_input.jsonl'\n    }\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "fe5c9d43-a1ee-4f1b-bd4b-988fe0194d50",
      "name": ".jsonl to base64",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        -80,
        64
      ],
      "parameters": {
        "options": {},
        "operation": "binaryToPropery"
      },
      "typeVersion": 1.1
    },
    {
      "id": "6e372e85-930f-4a71-96e0-5f3a5668f513",
      "name": "Decode base64",
      "type": "n8n-nodes-base.code",
      "position": [
        144,
        64
      ],
      "parameters": {
        "jsCode": "const base64Content = $input.first().json.data;\n\nconst jsonlContent = Buffer.from(base64Content, 'base64').toString('utf-8');\n\nconst lines = jsonlContent\n  .split('\\n')\n  .filter(line => line.trim())\n  .map(line => {\n    try {\n      return JSON.parse(line);\n    } catch (e) {\n      return null;\n    }\n  })\n  .filter(line => line !== null);\n\n// Extract answers from each line\nconst results = [];\nlet parsed = 0;\nlet errors = 0;\n\nfor (const line of lines) {\n  try {\n    if (line.error || line.response?.status_code !== 200) {\n      errors++;\n      continue;\n    }\n\n    const output = line.response?.body?.output || [];\n    const messageOutput = output.find(o => o.type === 'message');\n    const text = messageOutput?.content?.[0]?.text;\n\n    if (!text) {\n      errors++;\n      continue;\n    }\n\n    results.push(text);\n    parsed++;\n  } catch (e) {\n    errors++;\n  }\n}\n\n// Get batch metadata from the earlier node\nconst batch = $('If status = completed').first().json;\n\nreturn [{\n  json: {\n    id: batch.id,\n    status: batch.status,\n    output_file_id: batch.output_file_id,\n    result: results,\n    parsed,\n    errors\n  }\n}];"
      },
      "typeVersion": 2,
      "alwaysOutputData": false
    },
    {
      "id": "de97fb6a-2795-41f2-a2da-67aac90bd87e",
      "name": "Call Batch API to retrieve batch object",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -752,
        160
      ],
      "parameters": {
        "url": "=https://api.openai.com/v1/batches/{{ $json.id }}",
        "options": {},
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "6ac10207-84af-4386-96ee-af15e74e71fe",
      "name": "Download .jsonl result",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -304,
        64
      ],
      "parameters": {
        "url": "=https://api.openai.com/v1/files/{{ $json.output_file_id }}/content",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "9df69ab5-13c5-4a4f-8969-ed6bd4a640e0",
      "name": "Cron Job (5 mins)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1200,
        160
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "de0aebc0-d494-406c-8d2e-d3eb95e14930",
      "name": "Create a row in batch table",
      "type": "n8n-nodes-base.supabase",
      "position": [
        -304,
        -160
      ],
      "parameters": {
        "tableId": "openai_batches",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "=id",
              "fieldValue": "={{ $json.id }}"
            },
            {
              "fieldId": "batch_status",
              "fieldValue": "={{ $json.status }}"
            }
          ]
        }
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "6b78a91e-aeec-4777-800a-5b9751d88365",
      "name": "Get the earliest uncompleted batch",
      "type": "n8n-nodes-base.postgres",
      "position": [
        -976,
        160
      ],
      "parameters": {
        "sort": {
          "values": [
            {
              "column": "created_at"
            }
          ]
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "openai_batches",
          "cachedResultName": "openai_batches"
        },
        "where": {
          "values": [
            {
              "value": "failed",
              "column": "batch_status",
              "condition": "!="
            },
            {
              "value": "expired",
              "column": "batch_status",
              "condition": "!="
            },
            {
              "value": "cancelled",
              "column": "batch_status",
              "condition": "!="
            },
            {
              "value": "completed",
              "column": "batch_status",
              "condition": "!="
            }
          ]
        },
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "options": {},
        "operation": "select",
        "returnAll": true
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.6
    },
    {
      "id": "7477a084-cf40-4922-bf1a-edacd3e2f87c",
      "name": "Update status",
      "type": "n8n-nodes-base.supabase",
      "position": [
        -304,
        256
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "id",
              "keyValue": "={{ $json.id }}",
              "condition": "eq"
            }
          ]
        },
        "tableId": "openai_batches",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "updated_at",
              "fieldValue": "={{ $now }}"
            },
            {
              "fieldId": "batch_status",
              "fieldValue": "={{ $json.status }}"
            }
          ]
        },
        "matchType": "allFilters",
        "operation": "update"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "433a549e-b052-451b-9eba-309ceaea800c",
      "name": "Update status and result",
      "type": "n8n-nodes-base.supabase",
      "position": [
        368,
        64
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "id",
              "keyValue": "={{ $json.id }}",
              "condition": "eq"
            }
          ]
        },
        "tableId": "openai_batches",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "updated_at",
              "fieldValue": "={{ $now }}"
            },
            {
              "fieldId": "batch_status",
              "fieldValue": "={{ $json.status }}"
            },
            {
              "fieldId": "output_file_id",
              "fieldValue": "={{ $json.output_file_id }}"
            },
            {
              "fieldId": "result",
              "fieldValue": "={{ $json.result }}"
            }
          ]
        },
        "matchType": "allFilters",
        "operation": "update"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "602d3950-5d6d-4707-b51d-3e5f2550be8e",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1792,
        -960
      ],
      "parameters": {
        "width": 640,
        "height": 688,
        "content": "# OpenAI Batch API (FIFO with Supabase / Postgres)\n\n### How it works\nThis workflow uses OpenAI's **Batch API** to process multiple prompts at reduced cost, with Supabase as a persistent queue for tracking batch jobs.\n\n**Phase 1 \u2014 Submit:** Takes an array of text inputs and a system prompt, converts them into `.jsonl` batch format, uploads to OpenAI's Files API, submits a batch job, and records it in the `openai_batches` table.\n\n**Phase 2 \u2014 Poll & Retrieve (every 5 mins):** A cron job queries Supabase for the earliest uncompleted batch, checks its status via OpenAI, and either updates the status (if still processing) or downloads, decodes, and stores the completed results back into `openai_batches`.\n\n### Setup steps\n1. **Create the Supabase table** `openai_batches`:\n   - `id` (text, PK) \u2014 OpenAI batch ID\n   - `batch_status` (text, not null)\n   - `output_file_id` (text, nullable)\n   - `result` (text[], nullable) \u2014 array of LLM answers\n   - `created_at` (timestamptz, default now())\n   - `updated_at` (timestamptz, nullable)\n2. Add your **OpenAI API credentials** to all HTTP Request nodes.\n3. Add your **Supabase** and **Postgres** credentials.\n4. Replace mock data in **Start** with your actual `systemPrompt` and `inputs` array.\n\n### Customization\n- Change the model in **Convert to batch requests** (currently `gpt-5-mini`).\n- Adjust cron interval (currently every 5 mins).\n- For strict FIFO, set the Postgres select to `LIMIT 1`."
      },
      "typeVersion": 1
    },
    {
      "id": "16cd7a56-a24b-4e68-81cb-947eb446f75b",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1792,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 112,
        "content": "## \ud83d\udce4 Phase 1: Submit Batch\nConverts inputs to `.jsonl`, uploads to OpenAI, creates a batch job, and records it in `openai_batches`."
      },
      "typeVersion": 1
    },
    {
      "id": "46ddb6e7-1571-42a0-b9c8-bac07c947224",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1792,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 112,
        "content": "## \ud83d\udce5 Phase 2: Poll & Retrieve (cron)\nQueries `openai_batches` for uncompleted batches, checks OpenAI status, downloads + decodes results and stores them back if completed."
      },
      "typeVersion": 1
    },
    {
      "id": "b2374aa6-7301-4c46-9465-bd596b95fe8f",
      "name": "Submission done",
      "type": "n8n-nodes-base.noOp",
      "position": [
        -80,
        -160
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "e7977e36-2dbb-47d5-afe5-eaa2165d9f34",
      "name": "Retrieval done",
      "type": "n8n-nodes-base.noOp",
      "position": [
        592,
        64
      ],
      "parameters": {},
      "typeVersion": 1
    }
  ],
  "connections": {
    "Decode base64": {
      "main": [
        [
          {
            "node": "Update status and result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call Batch API": {
      "main": [
        [
          {
            "node": "Create a row in batch table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call Files API": {
      "main": [
        [
          {
            "node": "Call Batch API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    ".jsonl to base64": {
      "main": [
        [
          {
            "node": "Decode base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cron Job (5 mins)": {
      "main": [
        [
          {
            "node": "Get the earliest uncompleted batch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start (mock data)": {
      "main": [
        [
          {
            "node": "Convert to batch requests in .jsonl",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If status = completed": {
      "main": [
        [
          {
            "node": "Download .jsonl result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Update status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download .jsonl result": {
      "main": [
        [
          {
            "node": ".jsonl to base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update status and result": {
      "main": [
        [
          {
            "node": "Retrieval done",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create a row in batch table": {
      "main": [
        [
          {
            "node": "Submission done",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get the earliest uncompleted batch": {
      "main": [
        [
          {
            "node": "Call Batch API to retrieve batch object",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert to batch requests in .jsonl": {
      "main": [
        [
          {
            "node": "Call Files API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call Batch API to retrieve batch object": {
      "main": [
        [
          {
            "node": "If status = completed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

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

You provide a list of prompts and a system instruction, the workflow batches them into a single OpenAI Batch API request. The batch job is tracked in a Supabase openai_batches table. A cron job polls OpenAI every 5 minutes, and once the batch completes, the results are decoded…

Source: https://n8n.io/workflows/13238/ — 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

dummy_client - Shopify abandoned carts. Uses httpRequest, shopifyTrigger, whatsApp, supabase. Event-driven trigger; 25 nodes.

HTTP Request, Shopify Trigger, WhatsApp +2
Data & Sheets

This workflow acts as a junior finance research analyst for a UK boutique M&A or corporate finance team. It listens for Slack messages, classifies the request, gathers company or market data, and prod

HTTP Request, Google Drive, Google Docs +5
Data & Sheets

Agendamiento_v2. Uses n8n-nodes-evolution-api, redis, httpRequest, executeWorkflowTrigger. Event-driven trigger; 59 nodes.

N8N Nodes Evolution Api, Redis, HTTP Request +3
Data & Sheets

Cancelacion_v2. Uses executeWorkflowTrigger, redis, httpRequest, n8n-nodes-evolution-api. Event-driven trigger; 46 nodes.

Execute Workflow Trigger, Redis, HTTP Request +3
Data & Sheets

This workflow is a multi-system document synchronization pipeline built in n8n, designed to automatically sync and back up files between Microsoft SharePoint, Supabase/Postgres, and Google Drive.

HTTP Request, Supabase, Postgres +1