kvick api
Kvick API
Ask Kvick questions about a text or any JSON, and get the probability of every possible answer. One endpoint, three kinds of question.
- Base URL
- https://api.skena.ai, which Jev clients take as is (they add
/v1/systemone) - Model
- kvick-1
- Auth
Authorization: Bearer skl_…, a key you make on the dashboard's API key page (one per app or server; keys)- Paid from
- Skena AI credit, text only. Prices
Quickstart
Put your key in SKENA_API_KEY and ask one yes or no question:
curl https://api.skena.ai/v1/systemone \
-H "Authorization: Bearer $SKENA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kvick-1",
"state": "My card was charged twice, please refund one.",
"questions": {
"refund": {
"type": "noul",
"instructions": "Is the customer asking for money back?"
}
}
}'The answer comes back under the question's id. noul is the probability of yes:
{
"model": "kvick-1",
"answers": {
"refund": {
"type": "noul",
"noul": 0.97,
"stats": {}
}
},
"usage": {
"input_tokens": 151,
"output_tokens": 1
}
}The request
POST/v1/systemone
One request carries one state and as many questions about it as fit the limits below. Kvick reads the state once and answers every question against it: ten questions in one request read the state once, where ten requests read it ten times.
- Field
- modelrequired
- Type
- string
- Description
- Send "kvick-1", the one model today. The answer names the model that answered.
- Field
- staterequired
- Type
- string, object or array
- Description
- What the questions are about: a text, or any JSON (an email with its headers, a form, a list of rows). Images go inside it.
- Field
- questionsrequired
- Type
- object
- Description
- At least one question, keyed by an id you choose. Each answer comes back under the same id.
| Field | Type | Description |
|---|---|---|
| modelrequired | string | Send "kvick-1", the one model today. The answer names the model that answered. |
| staterequired | string, object or array | What the questions are about: a text, or any JSON (an email with its headers, a form, a list of rows). Images go inside it. |
| questionsrequired | object | At least one question, keyed by an id you choose. Each answer comes back under the same id. |
Questions
- Field
- typerequired
- Type
- "noul", "choice" or "score"
- Description
- Yes or no, multiple choice, or a rating.
- Field
- instructions
- Type
- string, number, boolean, object, array or null
- Description
- The question itself, usually a string. A choice whose options say everything can leave it out.
- Field
- criteria
- Type
- depends on type
- Description
- What the possible answers mean: see the three types below. Required for choice and score.
| Field | Type | Description |
|---|---|---|
| typerequired | "noul", "choice" or "score" | Yes or no, multiple choice, or a rating. |
| instructions | string, number, boolean, object, array or null | The question itself, usually a string. A choice whose options say everything can leave it out. |
| criteria | depends on type | What the possible answers mean: see the three types below. Required for choice and score. |
Each question has a type:
- type
noul- Asks
- Yes or no
- criteria
- Optional. An object with a
trueand/orfalsedescription, when yes and no need defining. - Answers with
noul: the probability of yes, from 0 to 1.
- type
choice- Asks
- Multiple choice
- criteria
- Required. An object of option name to description (
nullwhen the name says it all), 1 to 255 options. - Answers with
choice: the likeliest option;probabilities: every option's probability;confidence: 0 for a coin toss, 1 for certainty.
- type
score- Asks
- Rating
- criteria
- Required. An array of 2 to 10 level descriptions, lowest first. A level's number is its position, counting from 0.
- Answers with
score: the expected level (it can fall between levels);probabilitiesandlegend, keyed by level number;confidence.
| type | Asks | criteria | Answers with |
|---|---|---|---|
noul | Yes or no | Optional. An object with a true and/or false description, when yes and no need defining. | noul: the probability of yes, from 0 to 1. |
choice | Multiple choice | Required. An object of option name to description (null when the name says it all), 1 to 255 options. | choice: the likeliest option; probabilities: every option's probability; confidence: 0 for a coin toss, 1 for certainty. |
score | Rating | Required. An array of 2 to 10 level descriptions, lowest first. A level's number is its position, counting from 0. | score: the expected level (it can fall between levels); probabilities and legend, keyed by level number; confidence. |
A full example
One question of each type about the same message:
{
"model": "kvick-1",
"state": "My card was charged twice, please refund one.",
"questions": {
"refund": {
"type": "noul",
"instructions": "Is the customer asking for money back?"
},
"department": {
"type": "choice",
"criteria": {
"billing": null,
"technical": null,
"sales": null
}
},
"frustration": {
"type": "score",
"instructions": "How frustrated is the customer?",
"criteria": [
"calm",
"mildly annoyed",
"annoyed",
"frustrated",
"furious"
]
}
}
}{
"model": "kvick-1",
"answers": {
"refund": {
"type": "noul",
"noul": 0.97,
"stats": {}
},
"department": {
"type": "choice",
"choice": "billing",
"confidence": 0.93,
"probabilities": {
"billing": 0.95,
"technical": 0.03,
"sales": 0.02
},
"stats": {}
},
"frustration": {
"type": "score",
"score": 1.64,
"confidence": 0.45,
"legend": {
"0": "calm",
"1": "mildly annoyed",
"2": "annoyed",
"3": "frustrated",
"4": "furious"
},
"probabilities": {
"0": 0.08,
"1": 0.35,
"2": 0.44,
"3": 0.11,
"4": 0.02
},
"stats": {}
}
},
"usage": {
"input_tokens": 214,
"output_tokens": 3
}
}The probabilities in these examples are illustrative.
Answers
Every answer carries its type and the fields for that type. Probabilities and confidence have two decimals. stats is empty.
- type
noul- Fields
noul: the probability of yes, from 0 to 1.
- type
choice- Fields
choice: the likeliest option;probabilities: every option's probability;confidence: 0 for a coin toss, 1 for certainty.
- type
score- Fields
score: the expected level (it can fall between levels);probabilitiesandlegend, keyed by level number;confidence.
| type | Fields |
|---|---|
noul | noul: the probability of yes, from 0 to 1. |
choice | choice: the likeliest option; probabilities: every option's probability; confidence: 0 for a coin toss, 1 for certainty. |
score | score: the expected level (it can fall between levels); probabilities and legend, keyed by level number; confidence. |
usage reports what the call read and scored: see What counts below.
What counts
usage.input_tokensis what Kvick read: the state, every question and the format around them, and from Kvick's next release the tokens its images became (counted once, however many questions). With images,usage.image_tokenssays how many ofinput_tokensthe images took.usage.output_tokensis the number of answer steps it scored: one per question, a few more for a choice with over 26 options.- From Kvick's next release, an image is charged by the tokens Kvick really reads it as, at $0.10 per million. Each image token is charged at the text price times the image weight, today 440% (an image token counts as 4.4 text tokens): measured on Kvick's GPU, an image token takes about that much more time than a text token. Before the call runs, each image holds 65,536 tokens' worth of credit ($0.0288); the charge never goes above the hold.
- A refusal from Skena Cloud (an
error.code) costs nothing and does not count against the welcome gift's day. That includes a request Kvick cannot answer (400 invalid-decision-request): it is refused before it runs. - A call Kvick answered is charged for its input tokens, at $0.10 per million; output is free. On the welcome gift its input and output tokens also count against the day.
- A call Kvick could not answer (it was busy or failed) costs nothing, and is given back to the welcome gift's day.
- Only an answer is charged. If your connection drops before Kvick's answer reaches Skena Cloud, the call costs nothing and you get no answer: send it again.
Images
Images on the API come with Kvick's next release, paid from credit per image token. Until then, a request with an image in state is refused before it runs with 400 invalid-decision-request, and costs nothing.
From that release, put an image anywhere in state as {"type": "image", "url": "data:image/…;base64,…"}, beside any text or JSON about it. The object must have exactly those two keys: with any other key it is read as plain JSON text. Kvick reads the images first, then the rest of the state.
{
"model": "kvick-1",
"state": {
"receipt": {
"type": "image",
"url": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..."
},
"note": "Lunch with a customer, paid by card."
},
"questions": {
"is_receipt": {
"type": "noul",
"instructions": "Is the image a receipt?"
},
"over_100": {
"type": "noul",
"instructions": "Is the total more than 100?"
}
}
}- Base64 data URLs only (
data:image/png;base64,…, JPEG, WebP and the like). Links are refused, never fetched. - Up to 8 images per request, each at most 10 MiB decoded and 40 megapixels, inside the 24 MiB body.
- Charged by the tokens Kvick really reads the image as, at $0.10 per million. Each image token is charged at the text price times the image weight, today 440% (an image token counts as 4.4 text tokens): measured on Kvick's GPU, an image token takes about that much more time than a text token.
- Each image holds 65,536 tokens' worth ($0.0288) before the call runs, and the charge never goes above the hold. The answer's
usage.image_tokenssays how many ofinput_tokensthe images took. - An image that breaks these rules is refused with
422 image_rejected, which costs nothing. - Inside Skena Builder and Skena Servant, a Skena AI subscription's Kvick images count the same weighted tokens against the daily token limit from that release.
Limits
- The state plus the longest question: at most 32,768 tokens. The state plus every question: at most 65,536 tokens.
- A choice takes 1 to 255 options; a rating 2 to 10 levels.
- At most 1,024 scored steps per request: one per question, a few more for a choice with over 26 options.
- A request past any of these is refused before it runs with
400 invalid-decision-request. - The request body: at most 24 MiB, with its length declared in Content-Length (no chunked uploads). Every common HTTP client does this for a JSON body.
- On credit from Skena: no daily limit and no per-account bound on requests at a time, up to 120 credit calls a minute (Kvick and Klok together).
- On the welcome gift alone: 5,000 requests and 5,000,000 tokens (input plus output) per account per UTC day, 2 requests in flight or waiting at a time, and 5,000 credit calls in 24 hours.
- When Skena Cloud is full, a call waits up to 10 seconds for room before
503 labs-busy.
Errors
Two shapes reach you. Skena Cloud's refusals carry error.code; Kvick's own carry detail, in the Jev API's shapes.
From Skena Cloud
{
"error": {
"message": "daily-limit-reached",
"type": "skena_labs_error",
"code": "daily-limit-reached"
}
}- Status
- 400
- error.code
invalid-decision-request- Meaning
- Kvick cannot answer the request: a field is wrong, it is too long, or (until Kvick's next release) it holds an image. Refused before it runs, so it costs nothing.
- What to do
- Check the request against the rules on this page: Kvick's own field-by-field reason is not passed on.
- Status
- 400
- error.code
unreadable-body- Meaning
- The body could not be read.
- What to do
- Send the whole body again.
- Status
- 400
- error.code
invalid-credit-request-id- Meaning
- The Idempotency-Key header is longer than 128 bytes, or holds a line break or a NUL.
- What to do
- Send a shorter key.
- Status
- 401
- error.code
invalid-labs-key- Meaning
- No key, a malformed key, a revoked key, or a device key from Skena Builder or Skena Servant (the API takes Skena API keys only).
- What to do
- Use a key from your dashboard's API key page, or make a new one there.
- Status
- 402
- error.code
ai-credit-insufficient- Meaning
- Your credit does not cover this call.
- What to do
- Check your balance on the dashboard's Skena AI page.
- Status
- 402, 403
- error.code
ai-credit-unavailable- Meaning
- Nothing is left to pay for the call: the welcome gift is used up or expired, and the account has no other credit (403 while the welcome gift is paused).
- What to do
- Check your balance on the dashboard's Skena AI page.
- Status
- 403
- error.code
ai-credit-required- Meaning
- The account has no Skena AI credit. The API is paid from credit alone, whatever plan the account has.
- What to do
- Check your balance on the dashboard's Skena AI page.
- Status
- 403
- error.code
ai-credit-activation-required- Meaning
- Only the welcome gift is left, and no computer has activated it yet. Skena Cloud refuses before reading the body, so these calls cost nothing.
- What to do
- Sign in to Skena Builder or Skena Servant on a computer once, and connect Skena AI.
- Status
- 403
- error.code
account-unverified- Meaning
- The account's email is not verified.
- What to do
- Verify your email.
- Status
- 403
- error.code
account-suspended- Meaning
- The account is suspended.
- What to do
- Contact us.
- Status
- 409
- error.code
inference-request-already-recorded- Meaning
- The call reused an Idempotency-Key this account already sent, whatever happened to that first call.
- What to do
- Nothing ran or was charged again. The 409 does not repeat the first answer: to retry a call that failed, send a new key.
- Status
- 411
- error.code
length-required- Meaning
- The body did not declare its length (a chunked upload), or it is empty.
- What to do
- Send the JSON body with a Content-Length header.
- Status
- 413
- error.code
request-too-large- Meaning
- The body is over the 24 MiB limit.
- What to do
- Send a smaller state.
- Status
- 429
- error.code
too-many-concurrent-requests- Meaning
- On the welcome gift: more than 2 calls in flight or waiting on this account, or its calls together hold more than a third of Skena Cloud's room.
- What to do
- Wait for one to finish (Retry-After).
- Status
- 429
- error.code
daily-limit-reached- Meaning
- On the welcome gift: 5,000 requests today.
- What to do
- Retry-After says when the day resets, at 00:00 UTC.
- Status
- 429
- error.code
daily-token-limit-reached- Meaning
- On the welcome gift: 5,000,000 tokens today.
- What to do
- Retry-After says when the day resets, at 00:00 UTC.
- Status
- 429
- error.code
inference-attempt-limit- Meaning
- Calls paid from credit, Kvick and Klok together: over 120 in the last minute (on the welcome gift, also 5,000 in the last 24 hours).
- What to do
- Retry after the Retry-After seconds.
- Status
- 503
- error.code
labs-busy- Meaning
- Skena Cloud was full and the call waited 10 seconds for room.
- What to do
- Retry after a short delay (Retry-After).
- Status
- 503
- error.code
labs-not-serving- Meaning
- Kvick is not serving right now.
- What to do
- Retry later.
- Status
- 503
- error.code
credit-quote-unavailable- Meaning
- Skena Cloud could not price the call before running it.
- What to do
- Retry after a short delay.
- Status
- 503
- error.code
labs-store-unavailable- Meaning
- A temporary problem on our side.
- What to do
- Retry after a short delay.
- Status
- 503
- error.code
labs-cache-isolation-unavailable- Meaning
- A temporary problem on our side.
- What to do
- Retry after a short delay.
- Status
- 503
- error.code
labs-upstream-unavailable- Meaning
- Kvick could not be reached.
- What to do
- Retry after a short delay (Retry-After).
- Status
- 500
- error.code
labs-upstream-auth- Meaning
- Skena Cloud could not sign in to Kvick: our problem, not your key.
- What to do
- Retry later.
- Status
- 500
- error.code
labs-upstream-response- Meaning
- Kvick's answer was unreadable or too large to relay.
- What to do
- Retry; if it persists, ask fewer questions per request.
| Status | error.code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid-decision-request | Kvick cannot answer the request: a field is wrong, it is too long, or (until Kvick's next release) it holds an image. Refused before it runs, so it costs nothing. | Check the request against the rules on this page: Kvick's own field-by-field reason is not passed on. |
| 400 | unreadable-body | The body could not be read. | Send the whole body again. |
| 400 | invalid-credit-request-id | The Idempotency-Key header is longer than 128 bytes, or holds a line break or a NUL. | Send a shorter key. |
| 401 | invalid-labs-key | No key, a malformed key, a revoked key, or a device key from Skena Builder or Skena Servant (the API takes Skena API keys only). | Use a key from your dashboard's API key page, or make a new one there. |
| 402 | ai-credit-insufficient | Your credit does not cover this call. | Check your balance on the dashboard's Skena AI page. |
| 402, 403 | ai-credit-unavailable | Nothing is left to pay for the call: the welcome gift is used up or expired, and the account has no other credit (403 while the welcome gift is paused). | Check your balance on the dashboard's Skena AI page. |
| 403 | ai-credit-required | The account has no Skena AI credit. The API is paid from credit alone, whatever plan the account has. | Check your balance on the dashboard's Skena AI page. |
| 403 | ai-credit-activation-required | Only the welcome gift is left, and no computer has activated it yet. Skena Cloud refuses before reading the body, so these calls cost nothing. | Sign in to Skena Builder or Skena Servant on a computer once, and connect Skena AI. |
| 403 | account-unverified | The account's email is not verified. | Verify your email. |
| 403 | account-suspended | The account is suspended. | Contact us. |
| 409 | inference-request-already-recorded | The call reused an Idempotency-Key this account already sent, whatever happened to that first call. | Nothing ran or was charged again. The 409 does not repeat the first answer: to retry a call that failed, send a new key. |
| 411 | length-required | The body did not declare its length (a chunked upload), or it is empty. | Send the JSON body with a Content-Length header. |
| 413 | request-too-large | The body is over the 24 MiB limit. | Send a smaller state. |
| 429 | too-many-concurrent-requests | On the welcome gift: more than 2 calls in flight or waiting on this account, or its calls together hold more than a third of Skena Cloud's room. | Wait for one to finish (Retry-After). |
| 429 | daily-limit-reached | On the welcome gift: 5,000 requests today. | Retry-After says when the day resets, at 00:00 UTC. |
| 429 | daily-token-limit-reached | On the welcome gift: 5,000,000 tokens today. | Retry-After says when the day resets, at 00:00 UTC. |
| 429 | inference-attempt-limit | Calls paid from credit, Kvick and Klok together: over 120 in the last minute (on the welcome gift, also 5,000 in the last 24 hours). | Retry after the Retry-After seconds. |
| 503 | labs-busy | Skena Cloud was full and the call waited 10 seconds for room. | Retry after a short delay (Retry-After). |
| 503 | labs-not-serving | Kvick is not serving right now. | Retry later. |
| 503 | credit-quote-unavailable | Skena Cloud could not price the call before running it. | Retry after a short delay. |
| 503 | labs-store-unavailable | A temporary problem on our side. | Retry after a short delay. |
| 503 | labs-cache-isolation-unavailable | A temporary problem on our side. | Retry after a short delay. |
| 503 | labs-upstream-unavailable | Kvick could not be reached. | Retry after a short delay (Retry-After). |
| 500 | labs-upstream-auth | Skena Cloud could not sign in to Kvick: our problem, not your key. | Retry later. |
| 500 | labs-upstream-response | Kvick's answer was unreadable or too large to relay. | Retry; if it persists, ask fewer questions per request. |
A request that breaks a rule, such as a rating with one level, is refused before it runs:
{
"model": "kvick-1",
"state": "My card was charged twice, please refund one.",
"questions": {
"frustration": {
"type": "score",
"criteria": [
"calm"
]
}
}
}{
"error": {
"message": "invalid-decision-request",
"type": "skena_labs_error",
"code": "invalid-decision-request"
}
}From Kvick
- Status
- 422
- detail
detail[]- Meaning
- Kvick's engine refused a request that passed Skena Cloud's check: rarely
engine_rejected, and from Kvick's next releaseimage_rejectedfor an image it cannot read. Each entry names the field (loc), what is wrong (msg) and atype. It costs nothing. - What to do
- Fix the named field or image; if it looks right, tell us when it happened.
- Status
- 429
- detail
detail- Meaning
- Too many of your calls are already waiting in Kvick's own queue.
- What to do
- Retry after the Retry-After seconds.
- Status
- 503
- detail
detail- Meaning
- Kvick's own queue is full, or the call waited its 30 seconds there.
- What to do
- Retry after the Retry-After seconds.
- Status
- 529
- detail
overloaded_error- Meaning
- The decision engine is temporarily unavailable.
- What to do
- Retry after a short delay (Retry-After).
- Status
- 500
- detail
internal_error- Meaning
- Something failed inside Kvick.
- What to do
- Retry; if it persists, tell us when it happened.
| Status | detail | Meaning | What to do |
|---|---|---|---|
| 422 | detail[] | Kvick's engine refused a request that passed Skena Cloud's check: rarely engine_rejected, and from Kvick's next release image_rejected for an image it cannot read. Each entry names the field (loc), what is wrong (msg) and a type. It costs nothing. | Fix the named field or image; if it looks right, tell us when it happened. |
| 429 | detail | Too many of your calls are already waiting in Kvick's own queue. | Retry after the Retry-After seconds. |
| 503 | detail | Kvick's own queue is full, or the call waited its 30 seconds there. | Retry after the Retry-After seconds. |
| 529 | overloaded_error | The decision engine is temporarily unavailable. | Retry after a short delay (Retry-After). |
| 500 | internal_error | Something failed inside Kvick. | Retry; if it persists, tell us when it happened. |
Headers
Besides Authorization and Content-Type, a request may send:
- Header
Idempotency-Key- Meaning
- Optional: up to 128 bytes, a new one for each call. A call that reuses a key answers 409 inference-request-already-recorded and is neither run nor charged, so an automatic retry can never charge a call twice. The 409 does not repeat the first answer.
| Header | Meaning |
|---|---|
Idempotency-Key | Optional: up to 128 bytes, a new one for each call. A call that reuses a key answers 409 inference-request-already-recorded and is neither run nor charged, so an automatic retry can never charge a call twice. The 409 does not repeat the first answer. |
Response headers worth reading:
- Header
X-Decide-Request-Id- Meaning
- Kvick's id for the call (req_…), on its answers. Quote it when you report a problem.
- Header
X-Skena-Usage-Id- Meaning
- The call's entry in your credit history, once Skena Cloud has priced the call and set its cost aside.
- Header
Retry-After- Meaning
- Seconds to wait before retrying: on every 429, on labs-busy and labs-upstream-unavailable, and on Kvick's own 503 and 529.
| Header | Meaning |
|---|---|
X-Decide-Request-Id | Kvick's id for the call (req_…), on its answers. Quote it when you report a problem. |
X-Skena-Usage-Id | The call's entry in your credit history, once Skena Cloud has priced the call and set its cost aside. |
Retry-After | Seconds to wait before retrying: on every 429, on labs-busy and labs-upstream-unavailable, and on Kvick's own 503 and 529. |
Calling it well
- Ask several questions about one state in one request: Kvick reads the state once and answers every question against it.
- Make one key per app or server, and keep keys on your server: anyone with one can spend your account's credit, on Klok as well as Kvick. Revoke a key that leaks, and the others keep working.
- Set your own thresholds: a probability is more useful than a yes, so act on 0.9 where a mistake is costly and on 0.5 where it is not.
- Retry 429, 503 and 529 after the Retry-After seconds, and treat a 500 as temporary. Send each retry with a new Idempotency-Key.
Models
GET/v1/models
Lists every model your key can call, in one answer with two shapes: OpenAI's data (what OpenAI's SDKs read) and the Jev API's models (what Jev clients read, Kvick only). A model is listed only while Skena Cloud serves it.
{
"object": "list",
"data": [
{
"id": "klok-1",
"object": "model",
"owned_by": "skena",
"name": "Skena Klok",
"context_window": 262144
},
{
"id": "kvick-1",
"object": "model",
"owned_by": "skena",
"name": "Skena Kvick"
}
],
"models": [
{
"name": "kvick-1",
"description": "Skena Kvick: calibrated System One decisions (Jev-compatible)."
}
]
}It needs a valid key on an account with credit, and never wakes a model. Its refusals use the skena_inference_error envelope, for Kvick too.
Jev compatibility
Kvick speaks the Jev API: the same POST /v1/systemone request and the same answer shapes, and Kvick's own refusals use the Jev API's error shapes. Code written for Jev works by pointing it at https://api.skena.ai (Jev clients add /v1/systemone) with your key, and sending "model": "kvick-1". Skena Cloud's refusals (a key, credit, a limit) use their own error envelope.
Jev and TypeSafe are trademarks of their owners. Skena Labs is not affiliated with TypeSafe or Bespoke Labs.
OpenAPI
The same reference, machine-readable, for code generators, API clients and coding agents:
https://skena.ai/docs/kvick/openapi.json
- Kvick is a Skena Labs model: there is no uptime guarantee yet, and the API may change.
- Please do not send personal or sensitive data.