Documentation
One endpoint. You send messages, you get a reply. Every number on this page is enforced by the running server.
Quickstart
A streamed reply, in one command:
curl -N https://www.ai.latdlabs.in/api/v1/chat \
-H "content-type: application/json" \
-d '{
"messages": [
{ "role": "user", "content": "What is a haiku? One example, please." }
],
"stream": true
}'
What comes back, abbreviated:
data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"A hai"}}]}
data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
Messages
messages is the only required field. Each entry has a
role and content.
| role | what it does |
|---|---|
| system | Applies to the whole request. If you send several, the first is used. |
| user | Your turn. Can carry an image. |
| assistant | An earlier reply. Send these back to keep a conversation going. |
Request fields
| field | type | notes |
|---|---|---|
| messages | required | Turns, oldest first. Must not be empty. |
| stream | boolean | Default true. false returns one JSON object. |
| model | string | Accepted and echoed, but it does not select anything. |
| temperature | number | Accepted for compatibility. Does not change the answer. |
| max_tokens | integer | Accepted for compatibility. Reply length is set by the server. |
| system | string | Shorthand for a leading system message. |
Unknown fields are ignored, not rejected, so a request built for another provider still answers.
Response headers
| header | meaning |
|---|---|
| x-kova-image-turns | 1 if the request carried an image. |
| x-ratelimit-limit | Requests allowed per minute. |
| x-ratelimit-remaining | Left in the current window. |
| retry-after | Seconds to wait. Only on 429. |
Images
An image is a part inside a user message — not a
separate upload, and not a property of the conversation.
{
"role": "user",
"content": [
{ "type": "text", "text": "What is in this picture?" },
{ "type": "image_url",
"image_url": { "url": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ…" } }
]
}
url also takes a plain link
(https://example.com/chart.png). The server fetches
it before sending, so the address must be reachable without a login.
How images behave
- Read on the turn that carries it. Later turns are text — ask about the picture in the message that includes it.
- A picture alone still needs words. If the text part is empty, the server asks a plain "what do you see" for you.
- Formats: JPEG, PNG, WebP and GIF all work.
- One image per turn. If a turn has more than one, the first is used.
Streaming
With "stream": true the response is
text/event-stream: one line per event beginning
data:, then a blank line, ending in
data: [DONE].
| event | carries |
|---|---|
| delta | choices[0].delta.content — append it. |
| usage | Empty choices, a usage object. |
| finish | finish_reason: "stop" on the last one. |
| [DONE] | A bare string, not JSON. Stop reading. |
A minimal browser reader
const r = await fetch('/api/v1/chat', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
messages: [{ role: 'user', content: 'Tell me a short joke.' }],
}),
});
const reader = r.body.getReader();
const dec = new TextDecoder();
let buf = '', out = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buf += dec.decode(value, { stream: true });
let i;
while ((i = buf.indexOf('\n')) >= 0) {
const line = buf.slice(0, i).trim();
buf = buf.slice(i + 1);
if (!line.startsWith('data:')) continue;
const payload = line.slice(5).trim();
if (payload === '[DONE]') break;
const piece = JSON.parse(payload).choices?.[0]?.delta?.content;
if (piece) { out += piece; console.log(out); }
}
}
Plain JSON
Set "stream": false for scripts and cron jobs.
{
"object": "chat.completion",
"model": "glitchyapi",
"choices": [{
"index": 0,
"message": { "role": "assistant", "content": "Tokyo." },
"finish_reason": "stop"
}],
"usage": { "prompt_tokens": 8, "completion_tokens": 2, "total_tokens": 10 }
}
Errors
One JSON object, a status that means something, never a stack trace.
{
"error": {
"message": "`messages` must be a non-empty array.",
"type": "invalid_request_error",
"code": "missing_messages"
}
}
| status | when | code |
|---|---|---|
| 400 | Unreadable request: no messages, bad JSON, wrong types, over a size limit. | invalid_request_error |
| 413 | Image larger than the published ceiling. | image_too_large |
| 429 | Over 60 a minute. See retry-after. | too_many_requests |
| 502 | The service could not complete the answer. Retrying usually works. | service_error |
Limits
Published from the file that enforces them, so the two cannot drift.
| what | limit |
|---|---|
| requests / minute | 60 per client address |
| messages / request | 200 |
| characters / message | 100,000 |
| text / request | 400,000 characters |
| image, base64 | 3.4M chars (~3M bytes) |
| request body | 24 MB |
| remote image | 6 MB |
Under heavy free use a request may wait a few seconds before it starts. It still streams; it just starts late. That wait is not counted against the per-minute limit.
Service
Two endpoints that describe the service rather than answering a question.
GET /api/health
{
"ok": true,
"service": "glitchyapi",
"api": "/api/v1/chat",
"streaming": true,
"json": true,
"images": true,
"limits": "/docs#limits",
"docs": "/docs"
}
GET /api/v1/models
One entry, so tools that insist on listing models get an answer. There is nothing to choose.
{ "object": "list", "data": [
{ "id": "glitchyapi", "object": "model", "selectable": false }
] }
Questions
Do I need an API key?
No. Requests are sent as they are, with nothing in the headers.
Does it remember my last message?
No. Every request is independent. Send the earlier turns back inside
messages and the reply is written in context.
Why is the model field ignored?
There is one model behind the endpoint and the server chooses it. Accepting the field keeps clients built for other providers working.
Is there a JavaScript or Python library?
No, and none is needed — JSON in, JSON out. fetch
and requests are enough.
Is my conversation private?
Nothing is written to disk by this service, so there is no chat log to look at afterwards. It travels over HTTPS like any other request. Do not send secrets you would not send anywhere else.
Can I stream and send an image together?
Yes. The two are independent.