Formul8 Docs
User Guide

API Reference

OpenAI-compatible API for programmatic access to Formul8 AI agents

API Reference

Formul8 exposes an OpenAI-compatible API for programmatic access to its AI agents. If you use tools that work with the OpenAI API format (custom applications, automation scripts, or third-party integrations), you can point them at Formul8 with minimal changes.

Base URL: https://chat.formul8.ai


Authentication

All API requests require authentication via one of two methods:

Include your API key in the Authorization header:

Authorization: Bearer YOUR_API_KEY

API keys are managed through the Dashboard at users.formul8.ai. Your key is tied to your subscription tier, which determines agent access and rate limits.

Supabase JWT (Browser Sessions)

Browser-based applications can authenticate using a Supabase JWT obtained through Google OAuth. This is primarily used by F8Chat and other Formul8 web applications.

Authorization: Bearer YOUR_SUPABASE_JWT

Endpoints

POST /v1/chat/completions

The primary endpoint for chat interactions. Routes messages to the appropriate Formul8 agent and returns a response. Supports both streaming (SSE) and non-streaming modes.

Request:

curl -X POST https://chat.formul8.ai/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "model": "operator-agent",
    "messages": [
      {
        "role": "user",
        "content": "What are the THC limits for edibles in Colorado?"
      }
    ],
    "stream": false
  }'

Request Parameters:

ParameterTypeRequiredDescription
modelstringYesAgent model ID. One of: free-agent, pro-agent, operator-agent
messagesarrayYesArray of message objects with role and content
streambooleanNoEnable SSE streaming. Default: true

Message Object:

FieldTypeRequiredDescription
rolestringYesOne of: system, user, assistant
contentstringYesThe message content

Model IDs and Tier Mapping:

Model IDTier RequiredAdsMessage Limit
free-agentFreeYes5 per conversation
pro-agentProYesUnlimited
operator-agentOperator or TeamNoUnlimited

Your API key's associated tier determines which model IDs you can use. Requesting a model above your tier returns a response indicating the upgrade path.

Non-Streaming Response:

{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1708300000,
  "model": "operator-agent",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "In Colorado, the THC limits for edibles are regulated by the Marijuana Enforcement Division (MED)..."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 25,
    "completion_tokens": 150,
    "total_tokens": 175
  }
}

Streaming Response (SSE):

When stream: true (the default), the response is returned as Server-Sent Events:

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1708300000,"model":"operator-agent","choices":[{"index":0,"delta":{"role":"assistant","content":"In"},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1708300000,"model":"operator-agent","choices":[{"index":0,"delta":{"content":" Colorado"},"finish_reason":null}]}

...

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1708300000,"model":"operator-agent","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

Each data: line contains a JSON chunk with incremental content in choices[0].delta.content. The stream ends with data: [DONE].


POST /v1/responses

The Responses endpoint, used for web-search-augmented queries. This is the endpoint that F8Chat uses when the Search toggle is enabled.

Request:

curl -X POST https://chat.formul8.ai/v1/responses \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "model": "operator-agent",
    "input": "What are the latest changes to California cannabis regulations in 2026?"
  }'

Request Parameters:

ParameterTypeRequiredDescription
modelstringYesAgent model ID
inputstringYesThe user query

When the agent determines that web search would improve the response (based on a confidence threshold), the platform searches the web, retrieves relevant pages, and incorporates findings. Sources are cited in the response.


GET /v1/models

Returns the list of available models for your subscription tier.

Request:

curl https://chat.formul8.ai/v1/models \
  -H "Authorization: Bearer YOUR_API_KEY"

Response:

{
  "data": [
    {
      "id": "operator-agent",
      "object": "model",
      "owned_by": "formul8"
    }
  ]
}

The models returned depend on your subscription tier. Free tier sees free-agent, Pro sees pro-agent, Operator and Team see operator-agent.


GET /v1/endpoints

Returns the available endpoints configured for your organization, including plan-specific model assignments.

Request:

curl https://chat.formul8.ai/v1/endpoints \
  -H "Authorization: Bearer YOUR_API_KEY"

GET /health

Public health check endpoint. No authentication required.

Request:

curl https://chat.formul8.ai/health

Response:

{
  "status": "healthy",
  "uptime": 1234567,
  "agents": {
    "f8_agent": "healthy",
    "compliance": "healthy",
    "formulation": "healthy"
  },
  "version": "1.0.0",
  "timestamp": "2026-02-18T12:00:00Z"
}

Examples

Python (using the OpenAI SDK)

Since Formul8's API is OpenAI-compatible, you can use the official OpenAI Python SDK:

from openai import OpenAI

client = OpenAI(
    api_key="YOUR_FORMUL8_API_KEY",
    base_url="https://chat.formul8.ai/v1"
)

response = client.chat.completions.create(
    model="operator-agent",
    messages=[
        {"role": "user", "content": "What are the packaging requirements for edibles in Colorado?"}
    ]
)

print(response.choices[0].message.content)

JavaScript (using fetch)

const response = await fetch('https://chat.formul8.ai/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_FORMUL8_API_KEY'
  },
  body: JSON.stringify({
    model: 'operator-agent',
    messages: [
      { role: 'user', content: 'Help me formulate a 10mg THC gummy' }
    ],
    stream: false
  })
});

const data = await response.json();
console.log(data.choices[0].message.content);

JavaScript (Streaming)

const response = await fetch('https://chat.formul8.ai/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_FORMUL8_API_KEY'
  },
  body: JSON.stringify({
    model: 'operator-agent',
    messages: [
      { role: 'user', content: 'What extraction methods work best for live resin?' }
    ],
    stream: true
  })
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const chunk = decoder.decode(value);
  const lines = chunk.split('\n').filter(line => line.startsWith('data: '));

  for (const line of lines) {
    const data = line.slice(6); // Remove 'data: ' prefix
    if (data === '[DONE]') break;

    const parsed = JSON.parse(data);
    const content = parsed.choices[0]?.delta?.content;
    if (content) process.stdout.write(content);
  }
}

curl (Streaming)

curl -N -X POST https://chat.formul8.ai/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "model": "operator-agent",
    "messages": [
      {"role": "user", "content": "What are the transport manifest requirements in Oklahoma?"}
    ],
    "stream": true
  }'

The -N flag disables buffering so you see the SSE stream in real time.


Rate Limits

Rate limits vary by authentication method and subscription tier:

ContextLimit
Anonymous (no auth)500 messages per day per IP
Free tier5 messages per conversation; 100 API calls per month
Pro tier1,000 API calls per month
Operator tier5,000 API calls per month
Team tier50,000 API calls per month
Feedback endpoint5 per 15 minutes per IP

When you exceed a rate limit, the API returns a 429 Too Many Requests response. Retry after the indicated wait period.


Error Responses

The API returns standard HTTP status codes:

CodeMeaning
200Success
400Bad request — missing required fields or invalid parameters
401Unauthorized — missing or invalid API key
403Forbidden — your tier does not include the requested model
429Rate limit exceeded
500Internal server error

Error response body:

{
  "error": {
    "message": "Description of the error",
    "type": "invalid_request_error",
    "code": "missing_required_field"
  }
}

Conversation Context

To maintain conversation context across multiple messages, include the full message history in each request:

{
  "model": "operator-agent",
  "messages": [
    {"role": "user", "content": "What are Colorado's edible THC limits?"},
    {"role": "assistant", "content": "In Colorado, the THC limits for edibles..."},
    {"role": "user", "content": "What about beverages specifically?"}
  ],
  "stream": false
}

The API does not maintain server-side session state. Each request must include the full conversation history for context-aware responses. The API truncates history to the most recent 20 messages if the history is longer.


Next Steps


Last updated: 2026-02-18