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:
API Key (Recommended for Programmatic Access)
Include your API key in the Authorization header:
Authorization: Bearer YOUR_API_KEYAPI 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_JWTEndpoints
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:
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | Yes | Agent model ID. One of: free-agent, pro-agent, operator-agent |
messages | array | Yes | Array of message objects with role and content |
stream | boolean | No | Enable SSE streaming. Default: true |
Message Object:
| Field | Type | Required | Description |
|---|---|---|---|
role | string | Yes | One of: system, user, assistant |
content | string | Yes | The message content |
Model IDs and Tier Mapping:
| Model ID | Tier Required | Ads | Message Limit |
|---|---|---|---|
free-agent | Free | Yes | 5 per conversation |
pro-agent | Pro | Yes | Unlimited |
operator-agent | Operator or Team | No | Unlimited |
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:
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | Yes | Agent model ID |
input | string | Yes | The 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/healthResponse:
{
"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:
| Context | Limit |
|---|---|
| Anonymous (no auth) | 500 messages per day per IP |
| Free tier | 5 messages per conversation; 100 API calls per month |
| Pro tier | 1,000 API calls per month |
| Operator tier | 5,000 API calls per month |
| Team tier | 50,000 API calls per month |
| Feedback endpoint | 5 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:
| Code | Meaning |
|---|---|
200 | Success |
400 | Bad request — missing required fields or invalid parameters |
401 | Unauthorized — missing or invalid API key |
403 | Forbidden — your tier does not include the requested model |
429 | Rate limit exceeded |
500 | Internal 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
- Integrations Guide — MCP tools, widgets, and integration patterns
- Agent Catalog — understand what each agent can answer
- Pricing & Plans — API call limits by tier
Last updated: 2026-02-18