BuildQuickstarts by languageJavaScript & TypeScript

Quickstart: JavaScript and TypeScript

Node.js 18 and later include fetch, which is all a request needs. If you prefer typed helpers, the openai package works with Syntic’s base URL. Both are shown; the TypeScript examples run with tsx.

Create a project

mkdir syntic-hello && cd syntic-hello
npm init -y && npm pkg set type=module
npm install openai            # only for the library examples
npm install -D tsx typescript # only for the TypeScript examples

Send a request

// hello.js  (node hello.js)
const res = await fetch('https://app.syntic.ai/v1/chat/completions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SYNTIC_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'Amara S1.2',
    stream: false,
    messages: [{ role: 'user', content: 'Suggest a name for a board-game café.' }],
  }),
})
const data = await res.json()
if (!res.ok) throw new Error(`${res.status} ${data.error}`)
console.log(data.choices[0].message.content)
console.log(data.usage)
⚠️

Pass stream: false on every non-streaming call with the openai package. The API streams when stream is missing, and the package does not send the field unless you set it.

Typed request and response (fetch)

If you use fetch from TypeScript, a few types cover what you need:

type Role = 'system' | 'user' | 'assistant' | 'tool'
type Message = { role: Role; content: string | null; tool_calls?: ToolCall[]; tool_call_id?: string }
type ToolCall = { id: string; type: 'function'; function: { name: string; arguments: string } }
type Completion = {
  id: string
  choices: { index: number; message: Message; finish_reason: 'stop' | 'length' | 'tool_calls' }[]
  usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number }
}
type ApiError = { error: string; message?: string; detail?: string }
 
export async function complete(messages: Message[], model = 'Amara S1.2'): Promise<Completion> {
  const res = await fetch('https://app.syntic.ai/v1/chat/completions', {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.SYNTIC_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ model, stream: false, messages }),
  })
  const body = await res.json()
  if (!res.ok) {
    const e = body as ApiError
    throw new Error(`${res.status} ${e.error}${e.detail ? `: ${e.detail}` : ''}`)
  }
  return body as Completion
}

Stream the reply

const stream = await client.chat.completions.create({
  model: 'Amara Quick',
  stream: true,
  messages: [{ role: 'user', content: 'Describe the smell of a bookshop in two sentences.' }],
})
for await (const chunk of stream) process.stdout.write(chunk.choices[0]?.delta?.content ?? '')
process.stdout.write('\n')

Call a function

import OpenAI from 'openai'
import type { ChatCompletionMessageParam, ChatCompletionTool } from 'openai/resources/chat/completions'
 
const client = new OpenAI({ baseURL: 'https://app.syntic.ai/v1', apiKey: process.env.SYNTIC_API_KEY })
 
const tools: ChatCompletionTool[] = [{
  type: 'function',
  function: {
    name: 'table_free',
    description: 'Whether a table for a party size is free at a given time today.',
    parameters: {
      type: 'object',
      properties: { party: { type: 'integer' }, time: { type: 'string', description: 'HH:MM, 24-hour' } },
      required: ['party', 'time'],
    },
  },
}]
 
const messages: ChatCompletionMessageParam[] = [{ role: 'user', content: 'Can 5 of us play at 19:30?' }]
let r = await client.chat.completions.create({ model: 'Amara S1.2', stream: false, tools, messages })
const msg = r.choices[0].message
if (msg.tool_calls?.length) {
  messages.push(msg)
  for (const call of msg.tool_calls) {
    const args = JSON.parse(call.function.arguments || '{}')
    const result = { free: args.party <= 6, table: 'Window 2' } // replace with a real lookup
    messages.push({ role: 'tool', tool_call_id: call.id, content: JSON.stringify(result) })
  }
  r = await client.chat.completions.create({ model: 'Amara S1.2', stream: false, tools, messages })
}
console.log(r.choices[0].message.content)

The full loop is in Tool loop in TypeScript.

Handle errors

import OpenAI from 'openai'
 
try {
  await client.chat.completions.create({ model: 'Amara S1.2', stream: false, messages: [] })
} catch (err) {
  if (err instanceof OpenAI.APIError) {
    // Syntic's body is flat: { "error": "messages_required" }
    console.error(err.status, (err.error as any)?.error ?? err.message, err.headers?.['x-syntic-request-id'])
  } else throw err
}

The package retries 429 and 5xx responses twice by default; set maxRetries on the client to change that.

In the browser

Do not put a private key in front-end code. Call your own backend, which calls Syntic; see Call the API from a browser for both patterns and the CORS details.

Next steps

Was this page helpful?