CookbookBatch in TypeScript

Run a batch end to end in TypeScript

⚠️
Preview — this feature is being built; the design below is the spec.

The same overnight classification job in Node.js 20+, using built-in fetch.

import { readFile, writeFile } from 'node:fs/promises'
 
const API = 'https://app.syntic.ai/v1'
const H = { Authorization: `Bearer ${process.env.SYNTIC_API_KEY}` }
 
// 1. Build the JSONL input
const tickets = await loadTickets() // your data
const lines = tickets.map((t) => JSON.stringify({
  custom_id: `ticket-${t.id}`,
  endpoint: '/v1/chat/completions',
  body: {
    model: 'Amara Quick',
    max_tokens: 5,
    messages: [
      { role: 'system', content: 'Reply with one word: billing, technical, account or other.' },
      { role: 'user', content: t.text },
    ],
  },
}))
await writeFile('tickets.jsonl', lines.join('\n') + '\n')
 
// 2. Upload
const form = new FormData()
form.append('purpose', 'batch')
form.append('file', new Blob([await readFile('tickets.jsonl')]), 'tickets.jsonl')
const file = await (await fetch(`${API}/files`, { method: 'POST', headers: H, body: form })).json()
 
// 3. Create the batch
let batch = await (await fetch(`${API}/batches`, {
  method: 'POST',
  headers: { ...H, 'Content-Type': 'application/json', 'Idempotency-Key': 'tickets-2026-11-05' },
  body: JSON.stringify({ input_file_id: file.id }),
})).json()
 
// 4. Wait
while (!['completed', 'expired', 'canceled', 'failed'].includes(batch.status)) {
  await new Promise((r) => setTimeout(r, 60_000))
  batch = await (await fetch(`${API}/batches/${batch.id}`, { headers: H })).json()
}
 
// 5. Stream the results line by line
const res = await fetch(`${API}/batches/${batch.id}/results`, { headers: H })
const labels = new Map<string, string>()
const failed: string[] = []
let buf = ''
for await (const part of res.body!.pipeThrough(new TextDecoderStream())) {
  buf += part
  let i
  while ((i = buf.indexOf('\n')) >= 0) {
    const line = buf.slice(0, i); buf = buf.slice(i + 1)
    if (!line) continue
    const row = JSON.parse(line)
    if (row.result.type === 'succeeded') labels.set(row.custom_id, row.result.response.body.choices[0].message.content.trim())
    else failed.push(row.custom_id)
  }
}
console.log(labels.size, 'classified;', failed.length, 'to resubmit')

See Batches for limits and pricing.

Was this page helpful?