Skip to Content
KeuralDocs
Developers

Keural API

Send requests in OpenAI Chat Completions format with an API key.

Calls a model registered in Keural in OpenAI Chat Completions format.

The Base URL has the form https://<public API domain for each environment>/llm/v1. The public API domain differs by environment, so check it with your administrator.

MethodPathDescription
POST/llm/v1/chat/completionsCreate a conversation (streaming supported)
GET/llm/v1/modelsList of models connected to the key
GET/llm/v1/models/{model}Information about a model connected to the key

Paths for embeddings, image generation, voice, and the Responses API are not supported. Requests sent to these paths return 404.

Usage flow

StepWhat to do
1. Issue a keyChoose a model under Settings → API and get a key. Copy the model ID and the key.
2. Connect a clientEnter the Base URL, API key, and model ID into the app or code.
3. Use and manageCheck usage on the same screen, and reissue or revoke keys as needed.

Prerequisites

ItemDetails
Keural accountAn account you can sign in with
API menuIf there is no API menu in settings, ask your administrator to enable the feature.
Organization member permissionTo get a key in an organization workspace, the organization admin must first allow your API use. See Organization member API permissions.
Base URLThe public API address provided by your administrator
ClientAn app or SDK that supports OpenAI API-compatible connections

Issuing an API key

  1. Open Settings → API from the account menu. In the desktop and mobile apps, it is in the Tools section.
  2. On the API keys tab, choose the key owner (personal workspace or organization workspace) and the Model ID.
  3. If needed, set the New key expiration. If you leave it blank, the key does not expire until it is revoked.
  4. Press Issue key, then copy the key with Copy key and store it somewhere safe. The full key is displayed only once.
  • A key is connected to the chosen workspace and to one model. Usage is attributed to the person who issued the key.
  • A workspace can have only one usable key per model. To get a new key, reissue or revoke the existing key.
  • If you lose a key, reissue it. The previous key becomes invalid immediately after reissue.
  • If no model is available for issuing, ask your administrator to check the model's public setting, your plan, and the organization API permission.

Organization member API permissions

This section is for organization owners and admins. For members to get keys in an organization workspace, an admin must allow it first. The default for all members is blocked.

Organization management → Organization API consists of the following three sections. If the menu does not appear, the API feature is turned off or the account does not have organization management permission.

SectionWhat it does
API permissions by userTurn API use on or off for each member.
Issued keysCheck the status of keys issued in the organization and revoke them. You cannot view the full key.
API usageCheck the request and token usage of keys issued in the organization.
ActionResult
Turn permission onThe member can issue keys in the organization workspace.
Turn permission offNew issuance and calls with existing keys are blocked. Keys already issued are not deleted automatically, so revoke them under Issued keys if needed.

When a member leaves or is removed from the organization, the keys that the member received from the organization are revoked automatically.

Connecting to a client

Enter the following three values into an OpenAI API-compatible app (chat client, IDE extension, automation tool, and so on).

ValueWhat to enterNames used by apps
Base URLThe public API address your administrator provided (…/llm/v1)Base URL, API Base, Endpoint, Host
API KeyThe issued key (without leading or trailing spaces)API Key, Token, Secret Key, Authorization
ModelThe Model ID on the issue screen. It must exactly match the model connected to the keyModel, Model ID, Model Name
  1. In the app settings, add an OpenAI-compatible connection.
  2. Enter the Base URL and API Key, then save.
  3. Choose a model from the model list. The list shows only the one model connected to the key.
  4. Send a short message and check the response.

The request format is as follows.

TypeContent
Chat requestPOST <Base URL>/chat/completions
Model lookupGET <Base URL>/models
Authentication headerAuthorization: Bearer <issued key>
Content headerContent-Type: application/json

First request

export KEURAL_API_KEY='your_issued_key'
export KEURAL_BASE_URL='https://<public API domain for each environment>/llm/v1'
export MODEL_ID='Model ID from the issue screen'

curl "$KEURAL_BASE_URL/chat/completions" \
  -H "Authorization: Bearer $KEURAL_API_KEY" \
  -H 'Content-Type: application/json' \
  -d "{\"model\":\"$MODEL_ID\",\"messages\":[{\"role\":\"user\",\"content\":\"Hello.\"}]}"

The model in the request body must match the model ID connected to the key. If a path prefix such as provider/model-ID is attached, compare the part after the last /. If they differ, 403 is returned. The model in the response always contains the key's model ID.

from openai import OpenAI
import os

client = OpenAI(
    api_key=os.environ["KEURAL_API_KEY"],
    base_url=os.environ["KEURAL_BASE_URL"],
)
res = client.chat.completions.create(
    model=os.environ["MODEL_ID"],
    messages=[{"role": "user", "content": "Hello."}],
)
print(res.choices[0].message.content)

Supported request fields

Only the 18 top-level fields below are accepted. If any other field is sent, 400 is returned. Optional fields whose value is null are treated as not sent.

FieldRule
modelRequired. The model ID of the key
messagesRequired. 1 to 4,096 items
stream · stream_optionsSend stream_options only when stream: true. include_usage is supported.
toolsUp to 128. Function names are [A-Za-z0-9_-], 1 to 64 characters
tool_choicenone · auto · required · a specific function. required requires tools to be present.
parallel_tool_callsReturns 400 if the model does not support it
response_formattext · json_object · json_schema
max_tokens · max_completion_tokensSend only one of the two. If neither is sent, 4,096 is used (or a lower model limit if one is set). Exceeding the model limit returns 400.
temperature0 to 2. Returns 400 if the model does not support it
top_p0 to 1
frequency_penalty · presence_penalty-2 to 2
stopA string, or an array of up to 4 items
seedInteger
userUp to 256 characters
n1 only

Messages

RoleAccepted fields
system · developercontent, name
usercontent (a string, or an array of text and image parts), name
assistantcontent, name, refusal, tool_calls
toolcontent, tool_call_id
  • A tool message answers each id in the preceding tool_calls only once.
  • Image parts (image_url) go only in user messages for models that accept images. Use an http or https address, or a base64 data URI in PNG, JPEG, GIF, or WebP. detail is auto, low, or high.

Body

ItemLimit
Content-Typeapplication/json
Size1 MiB
JSON structureOne top-level object, nesting depth of 64 or less, no duplicate keys
Request timeUp to 600 seconds

Breaking a body rule returns 400, and param is body.

Streaming

If you include "stream": true in the request, the response is sent as SSE. Chunks are sent as data: ... events, and data: [DONE] is sent at the end. If you include "stream_options": {"include_usage": true}, one additional token usage chunk is sent just before [DONE].

When include_usage is included, every chunk contains usage: null, and the final usage chunk has an empty choices array. A non-streaming response contains usage only when both input and output tokens are confirmed.

The response id has the form chatcmpl-<x-request-id>. Every response header includes x-request-id.

If an error occurs during streaming, a data: event containing the error JSON is sent, and the connection ends without [DONE]. If a chunk exceeds 1 MiB or the response exceeds 16 MiB, the response ends with 503.

curl -N "$KEURAL_BASE_URL/chat/completions" \
  -H "Authorization: Bearer $KEURAL_API_KEY" \
  -H 'Content-Type: application/json' \
  -d "{\"model\":\"$MODEL_ID\",\"messages\":[{\"role\":\"user\",\"content\":\"Hello.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true}}"

Usage limits

Requests are limited per user, organization, and model by the number of concurrent requests, requests per minute (RPM), and tokens per minute (TPM). The actual values differ by environment.

Tokens per minute are deducted when a request is received. Input is counted as 1 token per 4 English characters, other characters as 1 token per character, and one image as 2,000 tokens. Output is deducted up to max_tokens (maximum 4,096), so setting the output limit only as high as you need leaves more room under the limit.

429 codeCauseRetry-After
rate_limit_exceededRPM, TPM, or concurrent requests exceeded60 seconds for RPM and TPM, 1 second for concurrent requests
api_busyThe server's processing capacity is exceededNone. Wait briefly and try again.
upstream_rate_limitThe model provider's limit is exceededNone

Key management

Under Settings → API → API keys, check the status of issued keys (valid, expired, or revoked), and reissue or revoke them.

A key is a 51-character string that starts with krl_llm_. Each key owner can have one key. If a key already exists, reissue it instead of issuing a new one. The expiration date can be set only to a date after today.

ActionResultNote
ReissueYou get a new key.The previous key becomes invalid immediately. Also update the key in any connected apps.
RevokeThe key is blocked immediately.Use this when the key may have been exposed. A revoked key cannot be restored.

Checking usage

Check API requests and the tokens they used in the following two places.

WhereWhat
Settings → API → API usage tabUsage of keys you issued in the current workspace
Organization management → Organization API, then API usage at the bottomUsage of all keys issued in the organization (organization owners and admins)

Search conditions

ConditionDescription
Start time · End timeBased on Korea time. The start time is included and the end time is not. If left blank, the last 7 days are searched, and you can search up to 90 days at a time.
Model IDSearches only requests for a specific model. If left blank, all models are searched.
Request stateChoose from All · Running · Completed · Failed · Cancelled · Unknown.

Set the conditions and press Search. To reflect requests you just sent, press Refresh.

Values on screen

  • Applied time range (KST): The time range actually searched. If the start or end time was left blank or the range exceeded 90 days, the adjusted range is shown.
  • Requests and the totals of Input tokens · Output tokens · Total tokens: Only confirmed values are added. Tokens not yet confirmed are shown as Unconfirmed.
  • Request list: Each request shows its request time, Model ID, request state, and input, output, and total tokens. 20 items are shown per page. Move between pages with Previous and Next.

Press Request details on a row to show the following values.

ItemDescription
Request IDThe value to provide when contacting support
Response typeStreaming or non-streaming response
Usage confirmation statusConfirmed · Partially confirmed · Unconfirmed
End time · Response time (ms)The time the request ended and the time it took
Cache read · Cache creation · Reasoning tokensShown only when the model returns these values

Usage is recorded for streaming requests even if you do not include stream_options.include_usage. If a request is interrupted partway through, it may remain as Partially confirmed or Unconfirmed.

Error format

Errors are returned in OpenAI-compatible format.

{
  "error": {
    "message": "Human-readable error description",
    "type": "authentication_error",
    "code": "invalid_api_key",
    "param": null
  }
}

type is determined by the status code, and code distinguishes the cause.

StatustypecodeCause
400invalid_request_errorinvalid_requestViolation of a field, value, or body rule. param names the problem field
400invalid_request_errorupstream_rejected_requestThe model provider rejected the request
401authentication_errorinvalid_api_keyKey format error, no key, expired, revoked, or reissued key, or more than one Authorization header
403permission_errorapi_disabledThe Keural API feature is turned off
403permission_errormodel_api_disabledPublic API access for the model is turned off
403permission_errormodel_not_allowedmodel differs from the key's model, or the member has no API permission
404invalid_request_errornot_foundA path that does not exist
405invalid_request_errormethod_not_allowedWrong method. The allowed methods are in the Allow header
429rate_limit_errorSee the Usage limits table aboveLimit exceeded
503server_errorupstream_timeoutThe response did not finish within 600 seconds
503server_errorinvalid_upstream_responseUnexpected response format from the provider, or stream size exceeded
503server_errordependency_unavailable · unsupported_providerTemporary internal problem. Include x-request-id when contacting support

Troubleshooting

SymptomResponse codeWhat to check
The model list is empty or the connection fails404 · 405Whether the Base URL ends with …/llm/v1
Authentication fails401Typos or leading and trailing spaces in the key, and whether it has expired, been revoked, or been reissued
The connection works but you cannot use the model403Whether the model ID matches the key's model, and the organization API permission
Responses are slow or fail occasionally429Usage limits
No response for a while5xxTry again after a short while
Usage is not shown or differs from actual usage—The search period and the workspace the key belongs to

If the problem persists, contact your administrator with the information below. Do not send the full key or the prompt content.

  • Time the problem occurred
  • Model ID used
  • Name and version of the client app
  • x-request-id in the response headers
  • Error message shown on screen

On this page