Send Kimi requests to POST https://api.guardrelay.ai/v1/chat/completions with the gd-… Bearer key.
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.
| Field | Purpose |
|---|---|
model | one of the four exact Kimi API IDs; any other id returns 404 model_not_found |
messages | a non-empty message history |
max_tokens or max_completion_tokens | maximum output tokens, a positive integer; the output part of the pre-authorized reserve |
stream | false for a regular response, true for SSE |
stream_options.include_usage | adds a separate final usage frame to the stream; stream_options takes no other key |
temperature, top_p | removed from the request on every model: Kimi accepts only fixed values |
top_k | a positive integer |
frequency_penalty, presence_penalty | only 0 is accepted |
n | only 1 is accepted |
tools, tool_choice, parallel_tool_calls | client functions; tool_choice: "required" is not accepted by kimi-for-coding and kimi-for-coding-highspeed |
reasoning_effort | any 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 |
thinking | kimi-for-coding and kimi-for-coding-highspeed only, exactly {"type": "enabled", "keep": "all"} |
response_format, stop, partial | response format, stop strings, and continuing a started answer |
prompt_cache_key | the prompt cache key, passed upstream as it is |
seed, metadata, prediction, service_tier, store, safety_identifier | their 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, verbosity | ordinary OpenAI fields. Client libraries set them on their own, so Guard does not block them and passes them upstream |
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.
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 parameter | Value |
|---|---|
| Method and address | POST https://api.guardrelay.ai/v1/files |
| Body | multipart/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 |
purpose | user_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 size | up to 10 MiB; an empty file is rejected |
| Bounds inside a document | PDF — 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 |
| Lifetime | 24 hours from upload, then the file is deleted automatically |
| Per-key quota | no 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 request | up 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 |
curl -X POST https://api.guardrelay.ai/v1/files \
-H "Authorization: Bearer $GUARD_API_KEY" \
-F "file=@report.pdf" \
-F "purpose=user_data"{
"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.
{
"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 operation | Request |
|---|---|
| List the key's files | GET /v1/files → {"object": "list", "data": [...]} |
| Metadata for one file | GET /v1/files/{file_id} |
| Download the original file | GET /v1/files/{file_id}/content — returns the bytes as an attachment |
| Delete immediately | DELETE /v1/files/{file_id} → {"id": "file_0Xk9…", "object": "file", "deleted": true} |
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.
| Endpoint | Request body | Response |
|---|---|---|
POST /v1/kimi/search | exactly one field — {"query": "string"} | {"object": "list", "data": [...]}, up to 20 results |
POST /v1/kimi/fetch | exactly one field — {"url": "https://…"} | {"content": "…", "content_type": "text/markdown; charset=utf-8", "truncated": false} |
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"}'{
"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.
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"}'{
"content": "# Heading\n\nPage text in Markdown…",
"content_type": "text/markdown; charset=utf-8",
"truncated": false
}| Limit | Value |
|---|---|
query | a non-empty string after trimming, up to 4096 characters and up to 16,384 UTF-8 bytes |
url | http/https only, up to 2048 characters, no whitespace, no user or password, no # fragment, port 80 or 443 only |
| Destination address | a 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 size | up 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 body | exactly one expected field. An extra field, a wrong field name, and broken JSON all three return 400 invalid_managed_service_request |