Skip to content
API referenceKimi · Chat Completions

Kimi · Chat Completions

Requests to four models through one OpenAI-compatible endpoint.

Send Kimi requests to POST https://api.guardrelay.ai/v1/chat/completions with the gd-… Bearer key.

Core fields

The list below is not a closed one. Guard does not reject an unknown field: it travels to the provider as it is, and Kimi answers for it. Kimi fixes temperature and top_p on all four models and refuses any other value, so on Chat Completions and Responses Guard removes both fields from the request: editors that always send temperature work unchanged. On /v1/messages the fields travel upstream as they are.

FieldPurpose
modelone of the four exact Kimi API IDs; any other id returns 404 model_not_found
messagesa non-empty message history
max_tokens or max_completion_tokensmaximum output tokens, a positive integer; the output part of the pre-authorized reserve
streamfalse for a regular response, true for SSE
stream_options.include_usageadds a separate final usage frame to the stream; stream_options takes no other key
temperature, top_premoved from the request on every model: Kimi accepts only fixed values
top_ka positive integer
frequency_penalty, presence_penaltyonly 0 is accepted
nonly 1 is accepted
tools, tool_choice, parallel_tool_callsclient functions; tool_choice: "required" is not accepted by kimi-for-coding and kimi-for-coding-highspeed
reasoning_effortany non-empty string value, on any of the four models. Your value reaches the wire exactly as sent: Guard neither translates it, nor substitutes one of its own, nor refuses a well-formed one. Kimi itself documents three levels, low, high and max, defaulting to max
thinkingkimi-for-coding and kimi-for-coding-highspeed only, exactly {"type": "enabled", "keep": "all"}
response_format, stop, partialresponse format, stop strings, and continuing a started answer
prompt_cache_keythe prompt cache key, passed upstream as it is
seed, metadata, prediction, service_tier, store, safety_identifiertheir shape is checked, then they go upstream unchanged. A live probe showed Kimi accepts them with 200, but the effect of each was not isolated; service_tier is auto or priority, store is false only
user, logprobs, top_logprobs, logit_bias, modalities, audio, web_search_options, reasoning, verbosityordinary OpenAI fields. Client libraries set them on their own, so Guard does not block them and passes them upstream

Example

bash
curl https://api.guardrelay.ai/v1/chat/completions \
  -H "Authorization: Bearer $GUARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kimi-for-coding",
    "messages": [{"role": "user", "content": "Reply with one word: READY"}],
    "max_tokens": 64,
    "stream": false
  }'

For a regular response, the text is in choices[0].message.content. Actual usage is used for final settlement after the response.

Files in a request

A document is first uploaded with a separate POST /v1/files call and then referenced in a chat request by its file_id. The upload body is multipart/form-data with a required file field and an optional purpose. No separate key and no separate authorization: the same gd-….

Upload parameterValue
Method and addressPOST https://api.guardrelay.ai/v1/files
Bodymultipart/form-data: exactly one file part in the file field and no more than one additional field. A second form field returns 400 invalid_multipart
purposeuser_data (default), file-extract, or assistants. Guard returns your own spelling, but with surrounding whitespace trimmed
Formats.pdf, .txt, .md, .json, .csv, .docx, .xlsx, .pptx
Single file sizeup to 10 MiB; an empty file is rejected
Bounds inside a documentPDF — up to 200 pages. .xlsx — up to 200 sheets, up to 20,000 rows per sheet, up to 200,000 cells per workbook, up to 32,768 characters in one cell. .pptx — up to 500 slides. .docx and .pptx — up to 50,000 blocks. Any document — up to 1000 sections
Lifetime24 hours from upload, then the file is deleted automatically
Per-key quotano more than 20 files and up to 50 MiB. The space is measured on the encrypted record, not on the original file: that record holds both the file and the text extracted from it. For .txt, .md, .json, and .csv it is about 3.6 times the file size, so 50 MiB of quota holds roughly 13–14 MiB of such documents. For compressed .docx, .xlsx, and .pptx the factor is higher — the archive is small, the text inside it is not
Inside one requestup to 20 file references. The shared 2 MiB bound is measured on the finished JSON block, not on plain text: it includes the wrapper, the notice field, every section anchor, and escaping. In our measurements that adds 5% on top of the text with dozens of sections and 13% with a thousand, so noticeably less than 2 MiB of your own text fits
bash
curl -X POST https://api.guardrelay.ai/v1/files \
  -H "Authorization: Bearer $GUARD_API_KEY" \
  -F "file=@report.pdf" \
  -F "purpose=user_data"
json
{
  "id": "file_0Xk9…",
  "object": "file",
  "bytes": 184320,
  "created_at": 1756200000,
  "expires_at": 1756286400,
  "filename": "report.pdf",
  "purpose": "user_data",
  "content_type": "application/pdf"
}

created_at and expires_at are Unix seconds. The gap between them is the file lifetime: after expires_at any request for that file_id answers 404 file_not_found.

In a chat request the file is referenced with an input_file block inside the content array of a user message. The question about the document goes in a sibling text block.

json
{
  "model": "k3",
  "max_tokens": 512,
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "input_file", "file_id": "file_0Xk9…"},
        {"type": "text", "text": "Summarize this document briefly."}
      ]
    }
  ]
}

Guard extracts the document text at upload time and substitutes it for the input_file block — at the position you wrote it, and only inside a user message. The block is replaced by an ordinary text block holding a guard_translated_document object: file_id, filename, content_type, and a sections list where each section carries text and an anchor such as file_0Xk9…#page=3, #slide=2, #sheet=1&part=1, or #section=4. To the model this is user data, not a system instruction; ask it to cite the anchors if you need page references. There is no separate citation field in the response. A request without input_file goes upstream unchanged and never touches the file store.

File operationRequest
List the key's filesGET /v1/files{"object": "list", "data": [...]}
Metadata for one fileGET /v1/files/{file_id}
Download the original fileGET /v1/files/{file_id}/content — returns the bytes as an attachment
Delete immediatelyDELETE /v1/files/{file_id}{"id": "file_0Xk9…", "object": "file", "deleted": true}

Managed search and page fetch

Two helper endpoints pull text out of the web so that you can put it into your next chat request yourself. They never call a model, accept no model, do not support streaming, and use the same gd-… key.

EndpointRequest bodyResponse
POST /v1/kimi/searchexactly one field — {"query": "string"}{"object": "list", "data": [...]}, up to 20 results
POST /v1/kimi/fetchexactly one field — {"url": "https://…"}{"content": "…", "content_type": "text/markdown; charset=utf-8", "truncated": false}
bash
curl -X POST https://api.guardrelay.ai/v1/kimi/search \
  -H "Authorization: Bearer $GUARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "python 3.14 release"}'
json
{
  "object": "list",
  "data": [
    {
      "url": "https://example.org/post",
      "title": "Page title",
      "snippet": "A short excerpt…",
      "date": "2026-08-20",
      "site_name": "example.org"
    }
  ]
}

Only url is guaranteed in a search result; title, snippet, date, and site_name appear when they arrived and passed the length check. A result without a usable url is dropped from the list, so data may hold fewer rows than were found.

bash
curl -X POST https://api.guardrelay.ai/v1/kimi/fetch \
  -H "Authorization: Bearer $GUARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.org/post"}'
json
{
  "content": "# Heading\n\nPage text in Markdown…",
  "content_type": "text/markdown; charset=utf-8",
  "truncated": false
}
LimitValue
querya non-empty string after trimming, up to 4096 characters and up to 16,384 UTF-8 bytes
urlhttp/https only, up to 2048 characters, no whitespace, no user or password, no # fragment, port 80 or 443 only
Destination addressa public domain name only, and it must contain a dot. Rejected: a name with no dot (localhost, metadata, any short host name), metadata.google.internal, the suffixes .home, .internal, .lan, .local, and .localhost, a literal IP address, and any name that resolves to a non-public address
Page sizeup to 2 MiB of response. A larger page is cut off by the transport and you receive 503 managed_service_unavailable with Retry-After: 1. The header promises a retry, but this page will not get smaller — pick another address
Request bodyexactly one expected field. An extra field, a wrong field name, and broken JSON all three return 400 invalid_managed_service_request

Documentation sections

On this page