curl --request POST \
--url https://api.perceptron.inc/v1/chat/completions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"messages": [
{
"content": "<string>",
"role": "system"
}
],
"model": "<string>",
"frequency_penalty": 0,
"max_completion_tokens": 1,
"n": 1,
"presence_penalty": 0,
"regex": "<string>",
"response_format": {
"type": "text"
},
"stream": false,
"stream_options": {
"include_usage": true
},
"temperature": 1,
"top_k": 1,
"top_p": 0,
"vision_config": {
"enable_thinking": true,
"internal_tools": {
"focus": true
}
}
}
'import requests
url = "https://api.perceptron.inc/v1/chat/completions"
payload = {
"messages": [
{
"content": "<string>",
"role": "system"
}
],
"model": "<string>",
"frequency_penalty": 0,
"max_completion_tokens": 1,
"n": 1,
"presence_penalty": 0,
"regex": "<string>",
"response_format": { "type": "text" },
"stream": False,
"stream_options": { "include_usage": True },
"temperature": 1,
"top_k": 1,
"top_p": 0,
"vision_config": {
"enable_thinking": True,
"internal_tools": { "focus": True }
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
messages: [{content: '<string>', role: 'system'}],
model: '<string>',
frequency_penalty: 0,
max_completion_tokens: 1,
n: 1,
presence_penalty: 0,
regex: '<string>',
response_format: {type: 'text'},
stream: false,
stream_options: {include_usage: true},
temperature: 1,
top_k: 1,
top_p: 0,
vision_config: {enable_thinking: true, internal_tools: {focus: true}}
})
};
fetch('https://api.perceptron.inc/v1/chat/completions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.perceptron.inc/v1/chat/completions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'messages' => [
[
'content' => '<string>',
'role' => 'system'
]
],
'model' => '<string>',
'frequency_penalty' => 0,
'max_completion_tokens' => 1,
'n' => 1,
'presence_penalty' => 0,
'regex' => '<string>',
'response_format' => [
'type' => 'text'
],
'stream' => false,
'stream_options' => [
'include_usage' => true
],
'temperature' => 1,
'top_k' => 1,
'top_p' => 0,
'vision_config' => [
'enable_thinking' => true,
'internal_tools' => [
'focus' => true
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.perceptron.inc/v1/chat/completions"
payload := strings.NewReader("{\n \"messages\": [\n {\n \"content\": \"<string>\",\n \"role\": \"system\"\n }\n ],\n \"model\": \"<string>\",\n \"frequency_penalty\": 0,\n \"max_completion_tokens\": 1,\n \"n\": 1,\n \"presence_penalty\": 0,\n \"regex\": \"<string>\",\n \"response_format\": {\n \"type\": \"text\"\n },\n \"stream\": false,\n \"stream_options\": {\n \"include_usage\": true\n },\n \"temperature\": 1,\n \"top_k\": 1,\n \"top_p\": 0,\n \"vision_config\": {\n \"enable_thinking\": true,\n \"internal_tools\": {\n \"focus\": true\n }\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.perceptron.inc/v1/chat/completions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"messages\": [\n {\n \"content\": \"<string>\",\n \"role\": \"system\"\n }\n ],\n \"model\": \"<string>\",\n \"frequency_penalty\": 0,\n \"max_completion_tokens\": 1,\n \"n\": 1,\n \"presence_penalty\": 0,\n \"regex\": \"<string>\",\n \"response_format\": {\n \"type\": \"text\"\n },\n \"stream\": false,\n \"stream_options\": {\n \"include_usage\": true\n },\n \"temperature\": 1,\n \"top_k\": 1,\n \"top_p\": 0,\n \"vision_config\": {\n \"enable_thinking\": true,\n \"internal_tools\": {\n \"focus\": true\n }\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.perceptron.inc/v1/chat/completions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"messages\": [\n {\n \"content\": \"<string>\",\n \"role\": \"system\"\n }\n ],\n \"model\": \"<string>\",\n \"frequency_penalty\": 0,\n \"max_completion_tokens\": 1,\n \"n\": 1,\n \"presence_penalty\": 0,\n \"regex\": \"<string>\",\n \"response_format\": {\n \"type\": \"text\"\n },\n \"stream\": false,\n \"stream_options\": {\n \"include_usage\": true\n },\n \"temperature\": 1,\n \"top_k\": 1,\n \"top_p\": 0,\n \"vision_config\": {\n \"enable_thinking\": true,\n \"internal_tools\": {\n \"focus\": true\n }\n }\n}"
response = http.request(request)
puts response.read_body{
"choices": [
{
"index": 1,
"message": {
"role": "system",
"content": "<string>",
"reasoning_content": "<string>"
},
"finish_reason": "stop"
}
],
"created": 1,
"id": "<string>",
"model": "<string>",
"object": "<string>",
"usage": {
"completion_tokens": 1,
"prompt_tokens": 1,
"total_tokens": 1
}
}{
"error": {
"code": null,
"message": "Model 'test' does not support video input",
"param": null,
"type": "invalid_request_error"
}
}{
"error": {
"code": null,
"message": "Invalid API key",
"param": null,
"type": "authentication_error"
}
}{
"error": {
"code": "rate_limit_exceeded",
"message": "Organization rate limit exceeded (300 requests/minute). Please retry after 30 seconds.",
"param": null,
"type": "rate_limit_error"
}
}{
"error": {
"code": null,
"message": "The server had an error while processing your request.",
"param": null,
"type": "server_error"
}
}{
"error": {
"code": "model_overloaded",
"message": "The model is currently overloaded with other requests. Please retry after a short wait.",
"param": null,
"type": "server_error"
}
}Chat Completions
Generate a response for a text or multimodal conversation, using the OpenAI chat completions request and response format.
curl --request POST \
--url https://api.perceptron.inc/v1/chat/completions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"messages": [
{
"content": "<string>",
"role": "system"
}
],
"model": "<string>",
"frequency_penalty": 0,
"max_completion_tokens": 1,
"n": 1,
"presence_penalty": 0,
"regex": "<string>",
"response_format": {
"type": "text"
},
"stream": false,
"stream_options": {
"include_usage": true
},
"temperature": 1,
"top_k": 1,
"top_p": 0,
"vision_config": {
"enable_thinking": true,
"internal_tools": {
"focus": true
}
}
}
'import requests
url = "https://api.perceptron.inc/v1/chat/completions"
payload = {
"messages": [
{
"content": "<string>",
"role": "system"
}
],
"model": "<string>",
"frequency_penalty": 0,
"max_completion_tokens": 1,
"n": 1,
"presence_penalty": 0,
"regex": "<string>",
"response_format": { "type": "text" },
"stream": False,
"stream_options": { "include_usage": True },
"temperature": 1,
"top_k": 1,
"top_p": 0,
"vision_config": {
"enable_thinking": True,
"internal_tools": { "focus": True }
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
messages: [{content: '<string>', role: 'system'}],
model: '<string>',
frequency_penalty: 0,
max_completion_tokens: 1,
n: 1,
presence_penalty: 0,
regex: '<string>',
response_format: {type: 'text'},
stream: false,
stream_options: {include_usage: true},
temperature: 1,
top_k: 1,
top_p: 0,
vision_config: {enable_thinking: true, internal_tools: {focus: true}}
})
};
fetch('https://api.perceptron.inc/v1/chat/completions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.perceptron.inc/v1/chat/completions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'messages' => [
[
'content' => '<string>',
'role' => 'system'
]
],
'model' => '<string>',
'frequency_penalty' => 0,
'max_completion_tokens' => 1,
'n' => 1,
'presence_penalty' => 0,
'regex' => '<string>',
'response_format' => [
'type' => 'text'
],
'stream' => false,
'stream_options' => [
'include_usage' => true
],
'temperature' => 1,
'top_k' => 1,
'top_p' => 0,
'vision_config' => [
'enable_thinking' => true,
'internal_tools' => [
'focus' => true
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.perceptron.inc/v1/chat/completions"
payload := strings.NewReader("{\n \"messages\": [\n {\n \"content\": \"<string>\",\n \"role\": \"system\"\n }\n ],\n \"model\": \"<string>\",\n \"frequency_penalty\": 0,\n \"max_completion_tokens\": 1,\n \"n\": 1,\n \"presence_penalty\": 0,\n \"regex\": \"<string>\",\n \"response_format\": {\n \"type\": \"text\"\n },\n \"stream\": false,\n \"stream_options\": {\n \"include_usage\": true\n },\n \"temperature\": 1,\n \"top_k\": 1,\n \"top_p\": 0,\n \"vision_config\": {\n \"enable_thinking\": true,\n \"internal_tools\": {\n \"focus\": true\n }\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.perceptron.inc/v1/chat/completions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"messages\": [\n {\n \"content\": \"<string>\",\n \"role\": \"system\"\n }\n ],\n \"model\": \"<string>\",\n \"frequency_penalty\": 0,\n \"max_completion_tokens\": 1,\n \"n\": 1,\n \"presence_penalty\": 0,\n \"regex\": \"<string>\",\n \"response_format\": {\n \"type\": \"text\"\n },\n \"stream\": false,\n \"stream_options\": {\n \"include_usage\": true\n },\n \"temperature\": 1,\n \"top_k\": 1,\n \"top_p\": 0,\n \"vision_config\": {\n \"enable_thinking\": true,\n \"internal_tools\": {\n \"focus\": true\n }\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.perceptron.inc/v1/chat/completions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"messages\": [\n {\n \"content\": \"<string>\",\n \"role\": \"system\"\n }\n ],\n \"model\": \"<string>\",\n \"frequency_penalty\": 0,\n \"max_completion_tokens\": 1,\n \"n\": 1,\n \"presence_penalty\": 0,\n \"regex\": \"<string>\",\n \"response_format\": {\n \"type\": \"text\"\n },\n \"stream\": false,\n \"stream_options\": {\n \"include_usage\": true\n },\n \"temperature\": 1,\n \"top_k\": 1,\n \"top_p\": 0,\n \"vision_config\": {\n \"enable_thinking\": true,\n \"internal_tools\": {\n \"focus\": true\n }\n }\n}"
response = http.request(request)
puts response.read_body{
"choices": [
{
"index": 1,
"message": {
"role": "system",
"content": "<string>",
"reasoning_content": "<string>"
},
"finish_reason": "stop"
}
],
"created": 1,
"id": "<string>",
"model": "<string>",
"object": "<string>",
"usage": {
"completion_tokens": 1,
"prompt_tokens": 1,
"total_tokens": 1
}
}{
"error": {
"code": null,
"message": "Model 'test' does not support video input",
"param": null,
"type": "invalid_request_error"
}
}{
"error": {
"code": null,
"message": "Invalid API key",
"param": null,
"type": "authentication_error"
}
}{
"error": {
"code": "rate_limit_exceeded",
"message": "Organization rate limit exceeded (300 requests/minute). Please retry after 30 seconds.",
"param": null,
"type": "rate_limit_error"
}
}{
"error": {
"code": null,
"message": "The server had an error while processing your request.",
"param": null,
"type": "server_error"
}
}{
"error": {
"code": "model_overloaded",
"message": "The model is currently overloaded with other requests. Please retry after a short wait.",
"param": null,
"type": "server_error"
}
}Overview
The Chat Completions API is fully compatible with OpenAI’s chat completions specification, supporting both text-only and multimodal (vision) requests. Use it to generate responses from Perceptron Mk1. Perceptron Mk1 triggers thinking and structured grounding through the typedvision_config body field.
vision_config
For perceptron-mk1, pass a top-level vision_config object alongside messages:
| Field | Values | Purpose |
|---|---|---|
annotation_format | "point" / "box" / "polygon" / "clip" | Grounded output format. clip is video-only. |
enable_thinking | true / false | Chain-of-thought reasoning. |
internal_tools.focus | true / false | Enable the focus tool — model can zoom into regions. Image only. |
When to enable_thinking
- On for text Q&A, captioning, OCR, and video clipping (
annotation_format: "clip"). - Off for spatial detection (
annotation_formatin"point","box","polygon").
Example: Grounded detection
curl https://api.perceptron.inc/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PERCEPTRON_API_KEY" \
-d '{
"model": "perceptron-mk1",
"messages": [
{ "role": "user",
"content": [
{ "type": "image_url",
"image_url": { "url": "<image-url>" } },
{ "type": "text",
"text": "Find every worker wearing PPE." }
]
}
],
"vision_config": { "annotation_format": "box" }
}'
Example: Video clipping
curl https://api.perceptron.inc/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PERCEPTRON_API_KEY" \
-d '{
"model": "perceptron-mk1",
"messages": [
{ "role": "user",
"content": [
{ "type": "video_url",
"video_url": { "url": "<video-url>" } },
{ "type": "text",
"text": "Clip the moment the worker scans the package." }
]
}
],
"vision_config": { "annotation_format": "clip", "enable_thinking": true }
}'
Example: Video from pre-decoded frames
If you’ve already sampled frames client-side, pass them inline with avideo_frames content part instead of a single video_url. Each frame carries an image_url (HTTP(S) URL or base64 data URL) and a timestamp_ms offset from the start of the clip. Provide between two and 256 frames, ordered by non-decreasing timestamp_ms. For optimal performance, follow a uniform sampling strategy aligned with the Video Token Counting guide.
curl https://api.perceptron.inc/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PERCEPTRON_API_KEY" \
-d '{
"model": "perceptron-mk1",
"messages": [
{ "role": "user",
"content": [
{ "type": "video_frames",
"video_frames": {
"frames": [
{ "image_url": { "url": "<frame-url>" }, "timestamp_ms": 0 },
{ "image_url": { "url": "<frame-url>" }, "timestamp_ms": 500 }
]
}
},
{ "type": "text",
"text": "What changes between these frames?" }
]
}
],
"vision_config": { "enable_thinking": true }
}'
Example: Image reasoning with focus
curl https://api.perceptron.inc/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PERCEPTRON_API_KEY" \
-d '{
"model": "perceptron-mk1",
"messages": [
{ "role": "user",
"content": [
{ "type": "image_url",
"image_url": { "url": "<image-url>" } },
{ "type": "text",
"text": "What is the serial number on the device in the corner?" }
]
}
],
"vision_config": {
"enable_thinking": true,
"internal_tools": { "focus": true }
}
}'
Streaming
Set"stream": true to receive Server-Sent Events (SSE). To get token usage, also set stream_options.include_usage: true — when enabled, usage is attached to the final chunk (the one with finish_reason: "stop"), immediately before data: [DONE].
curl https://api.perceptron.inc/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PERCEPTRON_API_KEY" \
-d '{
"model": "perceptron-mk1",
"messages": [
{ "role": "user",
"content": [
{ "type": "image_url",
"image_url": { "url": "<image-url>" } },
{ "type": "text", "text": "Describe this scene in detail." }
]
}
],
"vision_config": { "enable_thinking": true },
"stream": true,
"stream_options": { "include_usage": true }
}'
Best Practices
- Thinking pairs well with text and clipping; not with spatial detection. Turn
enable_thinkingon for text Q&A, captioning, OCR, andannotation_format: "clip". Turn it off for"point","box", and"polygon". - Leave
temperatureunset. The default is0.0(deterministic). Only set a non-zero value if you want more varied outputs. - Image format: HTTP(S) URLs and base64 data URLs are both supported. MIME types:
image/png,image/jpeg,image/webp,video/mp4,video/webm. - Inline frames vs.
video_url: Send a whole clip withvideo_url, or — if you’ve already sampled frames — pass them inline withvideo_frames(two to 256 frames,timestamp_msnon-decreasing). Inline frames give you precise control over exactly which frames the model sees; for optimal performance, sample uniformly in line with the Video Token Counting guide. - Token limits: 32K context, 8K output.
- Many questions, one input: batch them with Multilook instead of repeating the media on every call. The shared context is prefilled once, and reused tokens are billed at the cache-read rate.
Limits
| Limit | Value |
|---|---|
| Requests | 300/min |
| Request body size | 20 MB |
| Media upload | 20 GB per 48 hours |
Authorizations
Bearer token authentication using your Perceptron API key
Body
Conversation history listed in order. Supported roles: system, user, assistant.
Author role of the message as defined by the OpenAI Chat Completions spec.
- System
- User
- Assistant
Show child attributes
Show child attributes
The model to invoke. Use GET /v1/models to discover available model IDs. New Perceptron requests should use
perceptron-mk1.
Positive values discourage the model from repeating previously used tokens.
-2 <= x <= 2Maximum number of completion tokens to generate. Must fit within the selected model's context and output limits.
x >= 0Number of completions to generate. Only 1 is supported; greater values are rejected.
1 <= x <= 1Positive values encourage the model to introduce new concepts.
-2 <= x <= 2Regex pattern for constrained generation.
An object specifying the format that the model must output.
Setting to { "type": "json_schema", "json_schema": {...} } enables Structured Outputs
which ensures the model will match your supplied JSON schema.
- Option 1
- Option 2
Show child attributes
Show child attributes
Set to true for SSE streaming. When omitted, the API returns a single JSON response.
Optional streaming flags. Token usage is always reported in the final chunk of a streaming response.
Show child attributes
Show child attributes
Sampling temperature. Lower values yield deterministic replies; higher values explore more creative outputs.
0 <= x <= 2Top-k sampling. The model samples from the top k most likely tokens.
x >= 0Nucleus sampling probability. The model samples from the smallest token set whose cumulative probability exceeds this threshold.
x <= 1Perceptron vision-model controls (thinking, spatial output format, internal-tool toggles). Only supported on Perceptron-owned models.
Show child attributes
Show child attributes
Response
Chat completion generated successfully.