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 examplesSend 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?