curl --request PUT \
--url https://api.kitefrost.ai/v1/projects/{project_id}/byok \
--header 'Content-Type: application/json' \
--data '
{
"api_key": "<string>",
"provider": "openai",
"provider_url": "<string>",
"model_preference": "<string>",
"allow_platform_fallback": true
}
'import requests
url = "https://api.kitefrost.ai/v1/projects/{project_id}/byok"
payload = {
"api_key": "<string>",
"provider": "openai",
"provider_url": "<string>",
"model_preference": "<string>",
"allow_platform_fallback": True
}
headers = {"Content-Type": "application/json"}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
api_key: '<string>',
provider: 'openai',
provider_url: '<string>',
model_preference: '<string>',
allow_platform_fallback: true
})
};
fetch('https://api.kitefrost.ai/v1/projects/{project_id}/byok', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.kitefrost.ai/v1/projects/{project_id}/byok",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'api_key' => '<string>',
'provider' => 'openai',
'provider_url' => '<string>',
'model_preference' => '<string>',
'allow_platform_fallback' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.kitefrost.ai/v1/projects/{project_id}/byok"
payload := strings.NewReader("{\n \"api_key\": \"<string>\",\n \"provider\": \"openai\",\n \"provider_url\": \"<string>\",\n \"model_preference\": \"<string>\",\n \"allow_platform_fallback\": true\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.put("https://api.kitefrost.ai/v1/projects/{project_id}/byok")
.header("Content-Type", "application/json")
.body("{\n \"api_key\": \"<string>\",\n \"provider\": \"openai\",\n \"provider_url\": \"<string>\",\n \"model_preference\": \"<string>\",\n \"allow_platform_fallback\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.kitefrost.ai/v1/projects/{project_id}/byok")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"api_key\": \"<string>\",\n \"provider\": \"openai\",\n \"provider_url\": \"<string>\",\n \"model_preference\": \"<string>\",\n \"allow_platform_fallback\": true\n}"
response = http.request(request)
puts response.read_body{
"project_id": "<string>",
"has_key": true,
"source": "<string>",
"provider": "<string>",
"key_preview": "<string>",
"model_preference": "<string>",
"allow_platform_fallback": true,
"probe_status": "<string>",
"model_warning": "<string>"
}{
"error": "Forbidden",
"code": "forbidden",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "Your API key does not have the required scope.",
"doc_url": "https://docs.kitefrost.ai/errors/forbidden"
}{
"error": "Not found",
"code": "not_found",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "The requested resource does not exist.",
"doc_url": "https://docs.kitefrost.ai/errors/not_found"
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}{
"error": "Internal server error",
"code": "internal_error",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "An unexpected error occurred.",
"doc_url": "https://docs.kitefrost.ai/errors/internal_error"
}Set BYOK key
Store a tenant-supplied LLM provider API key (BYOK) for the project. The key is stored in memory and used instead of the shared platform key for all LLM calls in this project. Paid plans only (Pro and above) - see web/public/llms.txt § ‘All paid plans include BYOK’. Free-tier tenants in the treatment cohort of the BYOK experiment may also set a key after a successful live probe (KF_FREE_BYOK_COHORT).
curl --request PUT \
--url https://api.kitefrost.ai/v1/projects/{project_id}/byok \
--header 'Content-Type: application/json' \
--data '
{
"api_key": "<string>",
"provider": "openai",
"provider_url": "<string>",
"model_preference": "<string>",
"allow_platform_fallback": true
}
'import requests
url = "https://api.kitefrost.ai/v1/projects/{project_id}/byok"
payload = {
"api_key": "<string>",
"provider": "openai",
"provider_url": "<string>",
"model_preference": "<string>",
"allow_platform_fallback": True
}
headers = {"Content-Type": "application/json"}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
api_key: '<string>',
provider: 'openai',
provider_url: '<string>',
model_preference: '<string>',
allow_platform_fallback: true
})
};
fetch('https://api.kitefrost.ai/v1/projects/{project_id}/byok', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.kitefrost.ai/v1/projects/{project_id}/byok",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'api_key' => '<string>',
'provider' => 'openai',
'provider_url' => '<string>',
'model_preference' => '<string>',
'allow_platform_fallback' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.kitefrost.ai/v1/projects/{project_id}/byok"
payload := strings.NewReader("{\n \"api_key\": \"<string>\",\n \"provider\": \"openai\",\n \"provider_url\": \"<string>\",\n \"model_preference\": \"<string>\",\n \"allow_platform_fallback\": true\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.put("https://api.kitefrost.ai/v1/projects/{project_id}/byok")
.header("Content-Type", "application/json")
.body("{\n \"api_key\": \"<string>\",\n \"provider\": \"openai\",\n \"provider_url\": \"<string>\",\n \"model_preference\": \"<string>\",\n \"allow_platform_fallback\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.kitefrost.ai/v1/projects/{project_id}/byok")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"api_key\": \"<string>\",\n \"provider\": \"openai\",\n \"provider_url\": \"<string>\",\n \"model_preference\": \"<string>\",\n \"allow_platform_fallback\": true\n}"
response = http.request(request)
puts response.read_body{
"project_id": "<string>",
"has_key": true,
"source": "<string>",
"provider": "<string>",
"key_preview": "<string>",
"model_preference": "<string>",
"allow_platform_fallback": true,
"probe_status": "<string>",
"model_warning": "<string>"
}{
"error": "Forbidden",
"code": "forbidden",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "Your API key does not have the required scope.",
"doc_url": "https://docs.kitefrost.ai/errors/forbidden"
}{
"error": "Not found",
"code": "not_found",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "The requested resource does not exist.",
"doc_url": "https://docs.kitefrost.ai/errors/not_found"
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}{
"error": "Internal server error",
"code": "internal_error",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "An unexpected error occurred.",
"doc_url": "https://docs.kitefrost.ai/errors/internal_error"
}Body
PUT /v1/projects/{project_id}/byok
LLM provider API key
1LLM provider name (e.g. openai, anthropic)
Optional custom LLM provider base URL (e.g. for self-hosted OpenAI-compatible endpoints). Must be an HTTPS URL that passes SSRF validation - internal network addresses, cloud metadata endpoints, and non-HTTPS schemes are rejected.
Optional specific model the tenant prefers from their provider (a provider model id). Used as a hint when routing - if the model cannot satisfy the request's capability requirements, normal model selection is applied.
When False, requests that cannot be satisfied by the tenant's BYOK key fail with an explicit error instead of silently falling back to the platform's shared key. Defaults to True (permissive) to preserve current behavior.
Response
BYOK key stored