MENU navbar-image

Introduction

OpenAI-compatible API gateway for AI services. Use standard OpenAI SDKs with your kai API key.

Overview

kai provides an OpenAI-compatible API for AI services. You can use standard OpenAI SDKs by simply changing the base URL to your kai instance.

Authentication

All API endpoints require Bearer token authentication. Include your API key in the Authorization header:

Authorization: Bearer YOUR_API_KEY

Rate Limiting

All endpoints are rate-limited per API token. Check the X-RateLimit-* headers in every response:

Response Header Description
X-RateLimit-Limit Maximum requests per minute
X-RateLimit-Remaining Requests remaining in current window
X-RateLimit-Reset Unix timestamp when the window resets

When rate limited, you'll receive a 429 response with a Retry-After header.

Concurrency

POST endpoints enforce per-token concurrency limits. The number of simultaneous POST requests allowed depends on your product tier (default: 1). If all slots are occupied, the API returns 429 with error code concurrent_request_in_progress and a Retry-After header.

GET endpoints (models, usage) are never affected by the concurrency limit.

Usage Cap

Products can have a monthly usage cap. When set, the API returns these additional response headers on POST requests:

Response Header Description
X-Units-Remaining Requests remaining in current billing period
X-Trial-Units-Remaining Requests remaining in trial period (only during trial)

When the cap is reached, POST requests return 429 with usage_cap_exceeded. Only successful requests count against the cap.

Error Responses

All errors follow the OpenAI error format:

{
    "error": {
        "message": "Human-readable description",
        "type": "error_type",
        "code": "error_code",
        "param": "parameter_name"
    }
}
HTTP Status Type Code Description
400 invalid_request_error invalid_request Invalid request format or parameters
400 invalid_request_error text_extraction_failed Document text extraction failed
401 authentication_error invalid_api_key Invalid or missing API key
403 permission_error endpoint_not_allowed Token lacks required endpoint permission
403 permission_error subscription_inactive Subscription is not active
403 permission_error token_inactive Token has been deactivated
403 permission_error token_expired Token has expired
429 rate_limit_error rate_limit_exceeded Rate limit exceeded
429 rate_limit_error concurrent_request_in_progress Another request is still processing
429 rate_limit_error usage_cap_exceeded Monthly usage cap or trial limit exceeded
500 server_error internal_error Internal server error
503 server_error service_unavailable Service temporarily unavailable

Authenticating requests

To authenticate requests, include an Authorization header with the value "Bearer {YOUR_API_KEY}".

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

Contact office@kaino.at to obtain an API key.

Chat

Create a chat completion.

requires authentication

Generates a model response for the given conversation. Follows the OpenAI chat completions format. Token usage is included in successful responses and counted toward your billing period.

With stream: true the completion is sent as server-sent events, terminated by data: [DONE].

Example request:
curl --request POST \
    "https://kai.kaino.io/api/v1/chat/completions" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"messages\": [
        {
            \"role\": \"system\",
            \"content\": \"You are a helpful assistant.\"
        },
        {
            \"role\": \"user\",
            \"content\": \"Hello!\"
        }
    ],
    \"response_format\": {
        \"type\": \"json_object\"
    },
    \"stream_options\": {
        \"include_usage\": true
    },
    \"model\": \"\",
    \"model_type\": \"chat\",
    \"temperature\": 0.7,
    \"max_tokens\": 1024,
    \"top_p\": 1,
    \"reasoning_effort\": \"medium\",
    \"stream\": false,
    \"n\": 1,
    \"presence_penalty\": 0,
    \"frequency_penalty\": 0
}"
const url = new URL(
    "https://kai.kaino.io/api/v1/chat/completions"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "messages": [
        {
            "role": "system",
            "content": "You are a helpful assistant."
        },
        {
            "role": "user",
            "content": "Hello!"
        }
    ],
    "response_format": {
        "type": "json_object"
    },
    "stream_options": {
        "include_usage": true
    },
    "model": "",
    "model_type": "chat",
    "temperature": 0.7,
    "max_tokens": 1024,
    "top_p": 1,
    "reasoning_effort": "medium",
    "stream": false,
    "n": 1,
    "presence_penalty": 0,
    "frequency_penalty": 0
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
import requests
import json

url = 'https://kai.kaino.io/api/v1/chat/completions'
payload = {
    "messages": [
        {
            "role": "system",
            "content": "You are a helpful assistant."
        },
        {
            "role": "user",
            "content": "Hello!"
        }
    ],
    "response_format": {
        "type": "json_object"
    },
    "stream_options": {
        "include_usage": true
    },
    "model": "",
    "model_type": "chat",
    "temperature": 0.7,
    "max_tokens": 1024,
    "top_p": 1,
    "reasoning_effort": "medium",
    "stream": false,
    "n": 1,
    "presence_penalty": 0,
    "frequency_penalty": 0
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()
$client = new \GuzzleHttp\Client();
$url = 'https://kai.kaino.io/api/v1/chat/completions';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'messages' => [
                ['role' => 'system', 'content' => 'You are a helpful assistant.'],
                ['role' => 'user', 'content' => 'Hello!'],
            ],
            'response_format' => ['type' => 'json_object'],
            'stream_options' => ['include_usage' => true],
            'model' => '',
            'model_type' => 'chat',
            'temperature' => 0.7,
            'max_tokens' => 1024,
            'top_p' => 1.0,
            'reasoning_effort' => 'medium',
            'stream' => false,
            'n' => 1,
            'presence_penalty' => 0.0,
            'frequency_penalty' => 0.0,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Successful completion):


{
    "id": "chatcmpl-kai-abc123",
    "object": "chat.completion",
    "model": "Meta-Llama-3_3-70B-Instruct",
    "choices": [
        {
            "index": 0,
            "message": {
                "role": "assistant",
                "content": "Hello! How can I help you today?"
            },
            "finish_reason": "stop"
        }
    ],
    "usage": {
        "prompt_tokens": 25,
        "completion_tokens": 12,
        "total_tokens": 37
    }
}
 

Request      

POST api/v1/chat/completions

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

messages   object[]     

The messages to generate chat completions for. Must have at least 1 items.

role   string     

The role of the message author. Must be system, user, or assistant. Example: user

Must be one of:
  • system
  • user
  • assistant
content   string     

The content of the message. A string; with model_type vision (or on the vision stage endpoint) also a list of text and image_url parts with base64 data URLs (PNG, JPEG, WebP, GIF). Must be at least 1 character. Example: Hello!

response_format   object  optional    

Structured output, forwarded verbatim: {"type": "json_object"} or {"type": "json_schema", "json_schema": {"name": "...", "strict": true, "schema": {...}}}. {"type": "text"} is forwarded and changes nothing. Models that declare their supported kinds and do not list the requested one are skipped; when no model in the chain supports it the request fails with response_format_unsupported.

type   string  optional    

This field is required when response_format is present. Example: json_object

Must be one of:
  • text
  • json_object
  • json_schema
json_schema   object  optional    

This field is required when response_format.type is json_schema.

schema   object  optional    

This field is required when response_format.json_schema is present.

stream_options   object  optional    

Streaming options. include_usage: true adds a final chunk with an empty choices list and the token usage.

include_usage   boolean  optional    

Example: true

model   string  optional    

Accepted for OpenAI SDK compatibility but ignored. KAI selects the model based on the plan's configuration. Use GET /v1/models to introspect what's currently configured. Must not be greater than 255 characters.

model_type   string  optional    

kai extension to the OpenAI body (OpenAI SDKs send it through extra_body). Selects the model type whose configured models serve the request: chat, generation_alt, verifier, verifier_escalation, rerank, mentions, expansion, classifier, sql, title, vision, condense. Absent means chat. Every type other than chat needs to be enabled for your token (enabled_model_types in GET /v1/usage), else 403 model_type_not_allowed. Not accepted on the stage endpoints. Example: chat

Must be one of:
  • chat
  • generation_alt
  • verifier
  • verifier_escalation
  • rerank
  • mentions
  • expansion
  • classifier
  • sql
  • title
  • vision
  • condense
temperature   number  optional    

Sampling temperature between 0 and 2. Higher values make output more random. Must be at least 0. Must not be greater than 2. Example: 0.7

max_tokens   integer  optional    

Maximum number of tokens to generate. Range: 1-32768. Must be at least 1. Must not be greater than 32768. Example: 1024

top_p   number  optional    

Nucleus sampling probability. Range: 0-1. Must be at least 0. Must not be greater than 1. Example: 1

reasoning_effort   string  optional    

Reasoning effort for reasoning-capable models (gpt-oss, DeepSeek-R1, Qwen3). One of none, minimal, low, medium, high, xhigh. Forwarded only when the serving model lists the value as accepted; otherwise the model's configured value applies, else the parameter is omitted (provider default). Example: medium

Must be one of:
  • none
  • minimal
  • low
  • medium
  • high
  • xhigh
stream   boolean  optional    

Stream the completion as server-sent events (data: {chunk} lines, terminated by data: [DONE]). Fallback to another model happens only before the first event; an interruption afterwards ends the stream with an error event. Example: false

n   integer  optional    

Number of completions. Accepted for compatibility but always returns 1. Must be at least 1. Must not be greater than 10. Example: 1

stop   string  optional    

Stop sequences. Accepted for compatibility but not supported.

presence_penalty   number  optional    

Presence penalty (-2 to 2). Accepted for compatibility but not supported. Must be at least -2. Must not be greater than 2. Example: 0

frequency_penalty   number  optional    

Frequency penalty (-2 to 2). Accepted for compatibility but not supported. Must be at least -2. Must not be greater than 2. Example: 0

Audio

Transcribe audio or video.

requires authentication

Transcribes speech from an audio or video file. For video uploads, the audio track is automatically extracted before transcription. The file must contain at least one audio stream.

Compatible with the OpenAI Whisper API format.

Example request:
curl --request POST \
    "https://kai.kaino.io/api/v1/audio/transcriptions" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "model="\
    --form "language=de"\
    --form "prompt=This is a medical consultation."\
    --form "response_format=json"\
    --form "temperature=0"\
    --form "file=@/tmp/php6v85salev80b2vEkY3M" 
const url = new URL(
    "https://kai.kaino.io/api/v1/audio/transcriptions"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('model', '');
body.append('language', 'de');
body.append('prompt', 'This is a medical consultation.');
body.append('response_format', 'json');
body.append('temperature', '0');
body.append('file', document.querySelector('input[name="file"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());
import requests
import json

url = 'https://kai.kaino.io/api/v1/audio/transcriptions'
files = {
  'model': (None, ''),
  'language': (None, 'de'),
  'prompt': (None, 'This is a medical consultation.'),
  'response_format': (None, 'json'),
  'temperature': (None, '0'),
  'file': open('/tmp/php6v85salev80b2vEkY3M', 'rb')}
payload = {
    "model": "",
    "language": "de",
    "prompt": "This is a medical consultation.",
    "response_format": "json",
    "temperature": 0
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'multipart/form-data',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, files=files)
response.json()
$client = new \GuzzleHttp\Client();
$url = 'https://kai.kaino.io/api/v1/audio/transcriptions';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'multipart/form-data',
            'Accept' => 'application/json',
        ],
        'multipart' => [
            [
                'name' => 'model',
                'contents' => ''
            ],
            [
                'name' => 'language',
                'contents' => 'de'
            ],
            [
                'name' => 'prompt',
                'contents' => 'This is a medical consultation.'
            ],
            [
                'name' => 'response_format',
                'contents' => 'json'
            ],
            [
                'name' => 'temperature',
                'contents' => '0'
            ],
            [
                'name' => 'file',
                'contents' => fopen('/tmp/php6v85salev80b2vEkY3M', 'r')
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Successful transcription):


{
    "text": "Der Patient berichtet über anhaltende Kopfschmerzen seit zwei Wochen."
}
 

Request      

POST api/v1/audio/transcriptions

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: multipart/form-data

Accept        

Example: application/json

Body Parameters

file   file     

The audio or video file to transcribe. Max file size depends on your product plan. Supported formats: mp3, m4a, wav, webm, ogg, flac, aac (audio), mp4, webm, mov, avi, mkv (video with audio track). Must be a file. Must not be greater than 25600 kilobytes. Example: /tmp/php6v85salev80b2vEkY3M

model   string  optional    

The transcription model to use. Use GET /v1/models to list available models. If omitted, the default model for your plan is used.

Must be one of:
  • whisper-large-v3-turbo
language   string  optional    

The language of the audio as an ISO-639-1 code (e.g., de, en); a locale such as de-AT is reduced to de. Optional: an unsupported value is ignored and the language is detected from the audio. Example: de

Must be one of:
  • af
  • am
  • ar
  • as
  • az
  • ba
  • be
  • bg
  • bn
  • bo
  • br
  • bs
  • ca
  • cs
  • cy
  • da
  • de
  • el
  • en
  • es
  • et
  • eu
  • fa
  • fi
  • fo
  • fr
  • gl
  • gu
  • ha
  • he
  • hi
  • hr
  • ht
  • hu
  • hy
  • id
  • is
  • it
  • ja
  • jw
  • ka
  • kk
  • km
  • kn
  • ko
  • la
  • lb
  • ln
  • lo
  • lt
  • lv
  • mg
  • mi
  • mk
  • ml
  • mn
  • mr
  • ms
  • mt
  • my
  • ne
  • nl
  • nn
  • no
  • oc
  • pa
  • pl
  • ps
  • pt
  • ro
  • ru
  • sa
  • sd
  • si
  • sk
  • sl
  • sn
  • so
  • sq
  • sr
  • su
  • sv
  • sw
  • ta
  • te
  • tg
  • th
  • tk
  • tl
  • tr
  • tt
  • uk
  • ur
  • uz
  • vi
  • yi
  • yo
  • zh
prompt   string  optional    

Optional text to guide the model's style. Max 1000 characters. Must not be greater than 1000 characters. Example: This is a medical consultation.

response_format   string  optional    

The format of the transcript output: json, text, or verbose_json. Example: json

Must be one of:
  • json
  • text
  • verbose_json
temperature   number  optional    

Sampling temperature between 0 and 1. Lower is more deterministic. Must be at least 0. Must not be greater than 1. Example: 0

Documents

Anonymize documents by detecting and replacing PII with placeholders.

requires authentication

Upload 1 file (PDF, DOCX, images, plain text). The API extracts text (with OCR fallback for scanned documents), detects personally identifiable information, and replaces it with placeholders like [[NAME_483920174455]], [[EMAIL_612889304771]]. The number is derived from your product key and the value, so the same PII always yields the same placeholder.

Returns the sanitized text, a sanitized filename, and a mapping of placeholders to their original values.

Requires the documents_anonymize permission on your API token's product.

Example request:
curl --request POST \
    "https://kai.kaino.io/api/v1/documents/anonymize" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "pii_types[]=name"\
    --form "files[]=@/tmp/phph2qmruh4sddt0B4PqlB" 
const url = new URL(
    "https://kai.kaino.io/api/v1/documents/anonymize"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('pii_types[]', 'name');
body.append('files[]', document.querySelector('input[name="files[]"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());
import requests
import json

url = 'https://kai.kaino.io/api/v1/documents/anonymize'
files = {
  'pii_types[]': (None, 'name'),
  'files[]': open('/tmp/phph2qmruh4sddt0B4PqlB', 'rb')}
payload = {
    "pii_types": [
        "name"
    ]
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'multipart/form-data',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, files=files)
response.json()
$client = new \GuzzleHttp\Client();
$url = 'https://kai.kaino.io/api/v1/documents/anonymize';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'multipart/form-data',
            'Accept' => 'application/json',
        ],
        'multipart' => [
            [
                'name' => 'pii_types[]',
                'contents' => 'name'
            ],
            [
                'name' => 'files[]',
                'contents' => fopen('/tmp/phph2qmruh4sddt0B4PqlB', 'r')
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Successful anonymization):


{
    "object": "list",
    "data": [
        {
            "original_filename": "patient_report.pdf",
            "sanitized_filename": "patient_report.pdf",
            "sanitized_text": "Patient: [[NAME_483920174455]]\nDiagnose: [[MEDICAL_712045990318]]",
            "placeholders": {
                "[[NAME_483920174455]]": "Max Mustermann",
                "[[MEDICAL_712045990318]]": "Diabetes Typ 2"
            },
            "metadata": {
                "extraction_method": "pdf_parser",
                "used_ocr": false,
                "page_count": 2,
                "strategy": "data_minimization+ai"
            }
        }
    ]
}
 

Request      

POST api/v1/documents/anonymize

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: multipart/form-data

Accept        

Example: application/json

Body Parameters

files   file[]     

Array containing 1 document file to anonymize. Max file size depends on your product plan. Supported formats: PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, CSV, JSON, PNG, JPEG, TIFF, TXT. Must be a file. Must not be greater than 25600 kilobytes.

pii_types   string[]  optional    

Optional array of PII types to detect. If omitted or empty, all 30 types are detected. Valid types: name, email, phone, dob, address, insurance, iban, company, medical, credit_card, passport, vehicle, ip_address, url, username, vat_number, company_register, healthcare_number, tax_id, age, nationality, gender, religion, occupation, birth_year, linkedin, bic, dvr, case_number, criminal_record.

Must be one of:
  • name
  • email
  • phone
  • dob
  • address
  • insurance
  • iban
  • company
  • medical
  • credit_card
  • passport
  • vehicle
  • ip_address
  • url
  • username
  • vat_number
  • company_register
  • healthcare_number
  • tax_id
  • age
  • nationality
  • gender
  • religion
  • occupation
  • birth_year
  • linkedin
  • bic
  • dvr
  • case_number
  • criminal_record
existing_placeholders   string  optional    

DEPRECATED — you no longer need to send this. Placeholders are derived from your product key and the PII value, so the same value always yields the same [[TYPE_N]] across requests, sessions and devices. Sending a file and then a follow-up prompt already shares one placeholder namespace with no seeding.

Still accepted and validated unchanged, so existing integrations keep working. It no longer influences what anything is minted as: for any value the derivation already covers — which is every value — it is a no-op, and on a key collision the DERIVED entry wins. A seeded value that also appears in the document therefore comes back TWICE in the response map: once under your key, once under its derived key. sanitized_text uses the derived key whenever this request derived one — a seeded value the detectors never flag is masked with YOUR key instead — so always de-anonymize from the returned map, never from a key you remember.

Optional JSON-encoded map of placeholders to original PII values from earlier requests. MUST be sent as a JSON string in multipart requests. Keys must match the [[TYPE_N]] format (short legacy numbers still accepted); values are non-empty strings. Limits: 50 KB payload, 200 entries, 500 characters per value. Example: {"[[NAME_483920174455]]":"Max Mustermann"}.

Extract plain text from a single uploaded document.

requires authentication

Routing:

Requires the documents_extract permission on your API token's product.

Example request:
curl --request POST \
    "https://kai.kaino.io/api/v1/documents/extract" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "instructions=b"\
    --form "json_schema=n"\
    --form "file=@/tmp/phpo4ho9erllr5o068DRc0" 
const url = new URL(
    "https://kai.kaino.io/api/v1/documents/extract"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('instructions', 'b');
body.append('json_schema', 'n');
body.append('file', document.querySelector('input[name="file"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());
import requests
import json

url = 'https://kai.kaino.io/api/v1/documents/extract'
files = {
  'instructions': (None, 'b'),
  'json_schema': (None, 'n'),
  'file': open('/tmp/phpo4ho9erllr5o068DRc0', 'rb')}
payload = {
    "instructions": "b",
    "json_schema": "n"
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'multipart/form-data',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, files=files)
response.json()
$client = new \GuzzleHttp\Client();
$url = 'https://kai.kaino.io/api/v1/documents/extract';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'multipart/form-data',
            'Accept' => 'application/json',
        ],
        'multipart' => [
            [
                'name' => 'instructions',
                'contents' => 'b'
            ],
            [
                'name' => 'json_schema',
                'contents' => 'n'
            ],
            [
                'name' => 'file',
                'contents' => fopen('/tmp/phpo4ho9erllr5o068DRc0', 'r')
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Successful text extraction):


{
    "object": "extraction",
    "original_filename": "report.pdf",
    "text": "Page one content...",
    "metadata": {
        "extraction_method": "vision_ocr",
        "used_ocr": true,
        "page_count": 3,
        "word_count": 412,
        "file_type": "PDF"
    }
}
 

Request      

POST api/v1/documents/extract

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: multipart/form-data

Accept        

Example: application/json

Body Parameters

file   file     

1 document file to extract text from. Max file size depends on your product plan. Supported formats: PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, CSV, JSON, PNG, JPEG, TIFF, WebP, BMP, TXT. PDFs and images are processed through the cloud OCR backend configured on your plan; spreadsheets keep their tabular layout via PhpSpreadsheet; plain text / CSV / JSON pass through unchanged. Must be a file. Must not be greater than 25600 kilobytes. Example: /tmp/phpo4ho9erllr5o068DRc0

instructions   string  optional    

Optional. Free text (max 20,000 characters) telling the model how to read the document, e.g. which of two tables to use. Only consulted together with json_schema; sent on its own it is ignored. Must not be greater than 20000 characters. Example: b

json_schema   string  optional    

Optional. A JSON-encoded JSON Schema object (max 50 KB). When present, the upload is read by a vision model that fills the schema instead of transcribing the page, and the response carries the filled object as data. Image uploads only (PNG, JPEG, TIFF, WebP, BMP). For strict-mode models every object must declare "additionalProperties": false and list every property in required. Must not be greater than 51200 characters. Example: n

Strip metadata from a document and return the cleaned file.

requires authentication

Upload a single file (PDF, image, DOCX, XLSX, XLS, or text). Metadata (EXIF, author, company, timestamps, etc.) is removed and the cleaned file is returned as a binary download.

Requires the documents_strip_metadata permission on your API token's product.

Example request:
curl --request POST \
    "https://kai.kaino.io/api/v1/documents/strip-metadata" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "X-Metadata-Stripped: true" \
    --header "X-Strip-Method: imagemagick" \
    --header "X-Stripped-Fields: EXIF,IPTC,XMP,ICC" \
    --header "X-Original-Size: 245760" \
    --header "X-Sanitized-Size: 101500" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "file=@/tmp/phpmrv4dp03kotrdIPamuI" 
const url = new URL(
    "https://kai.kaino.io/api/v1/documents/strip-metadata"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "X-Metadata-Stripped": "true",
    "X-Strip-Method": "imagemagick",
    "X-Stripped-Fields": "EXIF,IPTC,XMP,ICC",
    "X-Original-Size": "245760",
    "X-Sanitized-Size": "101500",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('file', document.querySelector('input[name="file"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());
import requests
import json

url = 'https://kai.kaino.io/api/v1/documents/strip-metadata'
files = {
  'file': open('/tmp/phpmrv4dp03kotrdIPamuI', 'rb')}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'X-Metadata-Stripped': 'true',
  'X-Strip-Method': 'imagemagick',
  'X-Stripped-Fields': 'EXIF,IPTC,XMP,ICC',
  'X-Original-Size': '245760',
  'X-Sanitized-Size': '101500',
  'Content-Type': 'multipart/form-data',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, files=files)
response.json()
$client = new \GuzzleHttp\Client();
$url = 'https://kai.kaino.io/api/v1/documents/strip-metadata';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'X-Metadata-Stripped' => 'true',
            'X-Strip-Method' => 'imagemagick',
            'X-Stripped-Fields' => 'EXIF,IPTC,XMP,ICC',
            'X-Original-Size' => '245760',
            'X-Sanitized-Size' => '101500',
            'Content-Type' => 'multipart/form-data',
            'Accept' => 'application/json',
        ],
        'multipart' => [
            [
                'name' => 'file',
                'contents' => fopen('/tmp/phpmrv4dp03kotrdIPamuI', 'r')
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Cleaned file as binary download):


<<Binary file content>>
 

Example response (400, Metadata stripping failed):


{
    "error": {
        "message": "Failed to strip metadata from 'photo.jpg'",
        "type": "invalid_request_error",
        "param": "file",
        "code": "metadata_stripping_failed"
    }
}
 

Request      

POST api/v1/documents/strip-metadata

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

X-Metadata-Stripped        

Example: true

X-Strip-Method        

Example: imagemagick

X-Stripped-Fields        

Example: EXIF,IPTC,XMP,ICC

X-Original-Size        

Example: 245760

X-Sanitized-Size        

Example: 101500

Content-Type        

Example: multipart/form-data

Accept        

Example: application/json

Body Parameters

file   file     

1 document file to strip metadata from. Max file size depends on your product plan. Supported formats: PDF, DOCX, PPTX, XLSX, XLS, CSV, JSON, PNG, JPEG, TIFF, WebP, BMP, TXT. Must be a file. Must not be greater than 25600 kilobytes. Example: /tmp/phpmrv4dp03kotrdIPamuI

Models

List available models.

requires authentication

Returns models available for your API token. Use the model id values when calling chat or audio endpoints.

Example request:
curl --request GET \
    --get "https://kai.kaino.io/api/v1/models" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://kai.kaino.io/api/v1/models"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
import requests
import json

url = 'https://kai.kaino.io/api/v1/models'
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()
$client = new \GuzzleHttp\Client();
$url = 'https://kai.kaino.io/api/v1/models';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Available models):


{
    "object": "list",
    "data": [
        {
            "id": "Meta-Llama-3_3-70B-Instruct",
            "object": "model",
            "created": 1700000000,
            "owned_by": "kai"
        },
        {
            "id": "mistral-nemo-instruct-2407",
            "object": "model",
            "created": 1700000000,
            "owned_by": "kai"
        },
        {
            "id": "whisper-large-v3-turbo",
            "object": "model",
            "created": 1700000000,
            "owned_by": "kai"
        }
    ]
}
 

Request      

GET api/v1/models

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Usage

Get current billing period usage statistics.

requires authentication

Returns token status, product info, and usage counts for the current month. The status field indicates whether the token is currently usable (active) or not (inactive).

Example request:
curl --request GET \
    --get "https://kai.kaino.io/api/v1/usage" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://kai.kaino.io/api/v1/usage"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
import requests
import json

url = 'https://kai.kaino.io/api/v1/usage'
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()
$client = new \GuzzleHttp\Client();
$url = 'https://kai.kaino.io/api/v1/usage';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Usage statistics):


{
    "status": "active",
    "is_free": false,
    "is_trial": false,
    "trial_ends_at": null,
    "period": "2025-01",
    "product": "aegis-pro",
    "product_label": "Aegis PRO",
    "usage_label": "API Requests",
    "total_requests": 42,
    "total_ok": 40,
    "total_errors": 2,
    "total_tokens_in": 12500,
    "total_tokens_out": 3200,
    "rate_limit_per_minute": 10,
    "max_concurrent_requests": 1,
    "monthly_usage_cap": 100,
    "usage_remaining": 60,
    "trial_usage_limit": null,
    "trial_usage_remaining": null,
    "max_file_size": 26214400,
    "addons": [],
    "enabled_features": [],
    "enabled_endpoints": [
        "chat_completions",
        "audio_transcriptions",
        "documents_anonymize"
    ],
    "enabled_model_types": [
        "chat"
    ]
}
 

Request      

GET api/v1/usage

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

is_free   boolean     

Whether this is an anonymous free product key (no user account associated).

rate_limit_per_minute   integer     

Maximum requests per minute for this token.

max_concurrent_requests   integer     

Maximum simultaneous POST requests allowed.

monthly_usage_cap   integer     

Maximum successful requests per month. Null if unlimited.

usage_remaining   integer     

Remaining requests in current month. Null if unlimited.

trial_usage_limit   integer     

Total requests allowed during trial. Null if not on trial or no trial limit.

trial_usage_remaining   integer     

Remaining trial requests. Null if not on trial or no trial limit.

enabled_model_types   string[]     

The model_type values enabled for your token. Sending one to POST /v1/chat/completions also needs chat_completions in enabled_endpoints. chat is present whenever chat_completions is enabled.

Embeddings

Create embeddings.

requires authentication

Generates an embedding vector for the given input. Follows the OpenAI embeddings format. Input token usage is included in successful responses and counted toward your billing period (embeddings have no completion tokens, so prompt_tokens equals total_tokens).

Example request:
curl --request POST \
    "https://kai.kaino.io/api/v1/embeddings" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"input\": \"The quick brown fox jumps over the lazy dog.\",
    \"model\": \"\",
    \"dimensions\": 256,
    \"encoding_format\": \"float\",
    \"user\": \"\"
}"
const url = new URL(
    "https://kai.kaino.io/api/v1/embeddings"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "input": "The quick brown fox jumps over the lazy dog.",
    "model": "",
    "dimensions": 256,
    "encoding_format": "float",
    "user": ""
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
import requests
import json

url = 'https://kai.kaino.io/api/v1/embeddings'
payload = {
    "input": "The quick brown fox jumps over the lazy dog.",
    "model": "",
    "dimensions": 256,
    "encoding_format": "float",
    "user": ""
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()
$client = new \GuzzleHttp\Client();
$url = 'https://kai.kaino.io/api/v1/embeddings';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'input' => 'The quick brown fox jumps over the lazy dog.',
            'model' => '',
            'dimensions' => 256,
            'encoding_format' => 'float',
            'user' => '',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Successful embeddings response):


{
    "object": "list",
    "data": [
        {
            "object": "embedding",
            "index": 0,
            "embedding": [
                0.0023,
                -0.009,
                0.015
            ]
        }
    ],
    "model": "bge-multilingual-gemma2",
    "usage": {
        "prompt_tokens": 8,
        "total_tokens": 8
    }
}
 

Request      

POST api/v1/embeddings

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

input   string     

Text to embed. A string, an array of strings, or an array of token-id arrays. Forwarded verbatim to the backend. Example: The quick brown fox jumps over the lazy dog.

model   string  optional    

Accepted for OpenAI SDK compatibility but ignored. KAI selects the model based on the plan's configuration. Use GET /v1/models to introspect what's currently configured. Must not be greater than 255 characters.

dimensions   integer  optional    

Number of dimensions the resulting output embeddings should have. Forwarded to the backend only when supplied. Must be at least 1. Must not be greater than 8192. Example: 256

encoding_format   string  optional    

The format to return the embeddings in. One of float (default) or base64 (little-endian float32). Example: float

Must be one of:
  • float
  • base64
user   string  optional    

A unique identifier representing your end-user. Accepted for compatibility but not used. Must not be greater than 255 characters.

Endpoints

POST api/v1/recover

requires authentication

Example request:
curl --request POST \
    "https://kai.kaino.io/api/v1/recover" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"recovery_blob\": \"b\",
    \"cf-turnstile-response\": \"n\"
}"
const url = new URL(
    "https://kai.kaino.io/api/v1/recover"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "recovery_blob": "b",
    "cf-turnstile-response": "n"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
import requests
import json

url = 'https://kai.kaino.io/api/v1/recover'
payload = {
    "recovery_blob": "b",
    "cf-turnstile-response": "n"
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()
$client = new \GuzzleHttp\Client();
$url = 'https://kai.kaino.io/api/v1/recover';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'recovery_blob' => 'b',
            'cf-turnstile-response' => 'n',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/recover

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

recovery_blob   string     

Must not be greater than 512 characters. Example: b

cf-turnstile-response   string     

Must not be greater than 2048 characters. Example: n

requires authentication

Example request:
curl --request POST \
    "https://kai.kaino.io/api/v1/stats-link" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://kai.kaino.io/api/v1/stats-link"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
import requests
import json

url = 'https://kai.kaino.io/api/v1/stats-link'
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers)
response.json()
$client = new \GuzzleHttp\Client();
$url = 'https://kai.kaino.io/api/v1/stats-link';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));