IntegrationsCall OpenAI models with an integration

Call OpenAI models with an integration

Connect the OpenAI integration and use ctx.ai.openai in Func: chat for conversation and translation, json for reliable structured output, tools for a single tool call, image for generated images stored in your own project space. The API key stays on the server.

Connect the OpenAI integration and Func gets ctx.ai.openai: chat, translate, extract structured data, make a single tool call, generate images. The API key stays on the server and never appears in your code.

Connect OpenAI

Open Backend → Integrations in the editor, pick OpenAI, and fill in the form below. Saving makes a real call upstream, so a bad key fails right there instead of the first time a visitor uses the feature.

FieldWhat goes inRequired
API KeyThe sk- key, created in the OpenAI dashboardYes
Default modelUsed when a call omits one, e.g. gpt-4o-miniRecommended
Base URLBlank means https://api.openai.com/v1. Point it elsewhere to switch providerNo
Channel tagsA project can hold several configurations; tags tell them apart, see channelsNo

chat: one call

ctx.ai.openai.chat(params) makes one call and returns the complete result.

export function translate(input, ctx) {
  const r = ctx.ai.openai.chat({
    messages: [
      { role: 'system', content: 'Translate the user input into English. Output only the translation.' },
      { role: 'user', content: input.text },
    ],
    model: 'gpt-4o-mini',   // omit to use the integration's default
    temperature: 0.2,
    maxTokens: 512,
  })
  return { text: r.content, tokens: r.totalTokens }
}
ParameterNotes
messagesRequired. An array of { role, content }, where role is system / user / assistant / developer / tool
modelOmit to use the integration's default
temperatureNot sent at all when omitted
maxTokensNot sent at all when omitted
jsontrue strips the code fence and parses into data, see asking for JSON
tools / toolChoiceTool definitions, see a single tool call
extraEscape hatch merged into the request body, see escape hatches

The result:

FieldNotes
contentThe reply text. Empty when the model only asked for a tool, which is not an error
modelThe model that actually answered
finishReasonstop, length, tool_calls and so on
promptTokens / completionTokens / totalTokensUsage, for your own accounting
dataPresent only with json: true
toolCallsPresent only when the model asked for a tool

Ask for JSON with json: true

Getting JSON out of a model is the most common use of this capability: translation, extraction and classification all end there. With json: true the platform strips the code fence models love to add and parses what is left into data.

export function extract(input, ctx) {
  const r = ctx.ai.openai.chat({
    messages: [{ role: 'user', content: `Extract the name and city as JSON: ${input.text}` }],
    json: true,
  })
  return { name: r.data.name, city: r.data.city }
}

A single tool call

Pass tools and toolCalls comes back when the model wants one. The typical use is a single round: turn the user's sentence into one structured function call, run it yourself, then answer.

export function assistant(input, ctx) {
  const r = ctx.ai.openai.chat({
    messages: [{ role: 'user', content: input.text }],
    tools: [{
      type: 'function',
      function: {
        name: 'reschedule',
        description: 'Move a meeting to another date',
        parameters: {
          type: 'object',
          properties: { meetingId: { type: 'string' }, date: { type: 'string' } },
          required: ['meetingId', 'date'],
        },
      },
    }],
    toolChoice: 'auto',
  })

  if (r.toolCalls?.length) {
    const c = r.toolCalls[0]
    // c.arguments is already an object
    ctx.db.update('meetings', c.arguments.meetingId, { date: c.arguments.date })
    return { done: true }
  }
  return { reply: r.content }
}
toolCalls[i]Notes
idThe id of this call. Pass it back if you run a second round
nameThe function the model wants
argumentsAlready parsed into an object. undefined when the model emitted broken JSON
argumentsRawThe raw text, always present

A second round is yours to run: append the tool result to messages and call again.

const r2 = ctx.ai.openai.chat({
  messages: [
    { role: 'user', content: input.text },
    { role: 'assistant', toolCalls: r.toolCalls },              // replay what the model asked for
    { role: 'tool', toolCallId: r.toolCalls[0].id, content: JSON.stringify(result) },
  ],
})

The platform does not run that loop for you. When to stop, whether a tool may read the database, how the timeout is spent: only your site knows.

Generate images

ctx.ai.openai.image(params). Use upload: true and the image is stored in your project's own space, with a permanent URL back.

export const config = { timeoutMs: 120000 }   // image generation takes tens of seconds

export function cover(input, ctx) {
  const r = ctx.ai.openai.image({
    prompt: input.prompt,
    size: '1024x1024',
    quality: 'high',
    upload: true,
  })
  return { url: r.images[0].fileUrl }
}
ParameterNotes
promptRequired. A bare string works too: image('a red fox in the snow, watercolor')
modelDefaults to gpt-image-1. It does not reuse the chat model, those are different things
sizee.g. 1024x1024, 1536x1024
qualitylow / medium / high; dall-e-3 takes standard / hd
styledall-e-3 only: vivid / natural
backgroundgpt-image-1 only: transparent / opaque / auto
nHow many images, up to 4
uploadtrue stores them and returns fileUrl; otherwise you get imageBase64
filenameOnly used with upload. Defaults to a content hash

Returns { model, images: [...] }, where each image is { fileUrl | imageBase64, mimeType, bytes, revisedPrompt? }.

Escape hatches: extra and call

For fields the platform does not expose, use extra. It is merged into the request body as-is and cannot break messages:

ctx.ai.openai.chat({
  messages,
  extra: { top_p: 0.9, seed: 42, response_format: { type: 'json_object' } },
})

For endpoints other than /chat/completions, use call(path, body). The body goes up as-is, the upstream JSON comes back as-is, and the key still never enters the sandbox:

const r = ctx.ai.openai.call('/embeddings', {
  model: 'text-embedding-3-small',
  input: input.text,
})
const vector = r.data[0].embedding

Several channels: via(tag)

A project can hold several OpenAI configurations, say a cheap model for classification and a strong one for writing, or one official endpoint and one self-hosted gateway. Tag each configuration and pick one with via():

ctx.ai.openai.via('cheap').chat({ messages })
ctx.ai.openai.via('strong').chat({ messages })

Without via() the configuration tagged default is used.

Reading the errors

ErrorWhat to fix
ai integration has no api keyThe integration is not connected, or the key was cleared
model is requiredNo model in the call and no default on the integration
message at index N has empty contentOne message has empty content
message at index N has role tool but no toolCallIdA second round without toolCallId
ai response is not valid jsonjson: true but the model did not return JSON. The error carries a preview, so you can tell a vague prompt from a model that went off
the image provider returned a temporary urlThe gateway ignored the request for image bytes. Use call() and handle it yourself, or use a gateway that supports b64_json
invalid nAt most 4 images per call

Upstream errors (out of quota, unknown model, rate limited) keep their message but come back as 400 or 502. An upstream 401 is never passed through as-is, since that would make the page think the visitor is signed out.

What this does not do

Not includedWhy
Multi-turn agent loopsThe stopping rule, the round budget and tool permissions are yours. A single tool call is supported; the loop is your code
StreamingA Func returns one value. For a typewriter effect, push it yourself with ctx.sse
Caching and deduplicationOnly your site knows what counts as the same request. Keep your own table
Platform creditsThis is your own API key and your own bill; the platform stays out of it

See also: More integrations, Text to speech, Func backend capabilities.

Render diagnostics