Create Agent
Create an AI agent within a gateway, bound to a provider and upstream secret, with optional default model and budget configuration.
curl --request POST \
--url https://api.knoxcall.com/v1/ai-gateway/gateways/{gatewayId}/agents \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Support Bot",
"slug": "support-bot",
"default_model": "claude-sonnet-5"
}
'import requests
url = "https://api.knoxcall.com/v1/ai-gateway/gateways/{gatewayId}/agents"
payload = {
"name": "Support Bot",
"slug": "support-bot",
"default_model": "claude-sonnet-5"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({name: 'Support Bot', slug: 'support-bot', default_model: 'claude-sonnet-5'})
};
fetch('https://api.knoxcall.com/v1/ai-gateway/gateways/{gatewayId}/agents', 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.knoxcall.com/v1/ai-gateway/gateways/{gatewayId}/agents",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Support Bot',
'slug' => 'support-bot',
'default_model' => 'claude-sonnet-5'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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.knoxcall.com/v1/ai-gateway/gateways/{gatewayId}/agents"
payload := strings.NewReader("{\n \"name\": \"Support Bot\",\n \"slug\": \"support-bot\",\n \"default_model\": \"claude-sonnet-5\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.post("https://api.knoxcall.com/v1/ai-gateway/gateways/{gatewayId}/agents")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Support Bot\",\n \"slug\": \"support-bot\",\n \"default_model\": \"claude-sonnet-5\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.knoxcall.com/v1/ai-gateway/gateways/{gatewayId}/agents")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Support Bot\",\n \"slug\": \"support-bot\",\n \"default_model\": \"claude-sonnet-5\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"gateway_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"slug": "<string>",
"status": "active",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"agent_url": "<string>",
"tenant_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"description": "<string>",
"primary_route_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"fallback_route_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"model_allowlist": [
"<string>"
],
"model_denylist": [
"<string>"
],
"default_model": "<string>",
"model_rewrite": {},
"budget_daily_usd": 123,
"budget_monthly_usd": 123,
"budget_per_call_max_tokens": 123,
"budget_overage_action": "<string>",
"fallback_agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"pii_redact_policy_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"pii_detokenize_response": true,
"pii_request_mode": "off",
"pii_response_mode": "redact",
"pii_streaming_holdback_chars": 123,
"pii_streaming_mode": "<string>",
"cache_mode": "<string>",
"cache_ttl_seconds": 123,
"cache_similarity_threshold": 123,
"cache_embedding_model": "<string>",
"streaming_enabled": true,
"firewall_policy_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"tool_allowlist": [
"<string>"
],
"output_schema": {},
"output_validation_action": "<string>",
"data_residency_region": "us",
"cmek_key_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"paused_reason": "<string>",
"tags": {},
"provider": "<string>",
"routing_policy": {
"retry_on": [
"429"
],
"max_attempts": 3,
"backoff_ms": 15000,
"backoff_multiplier": 5.5,
"max_backoff_ms": 15000,
"respect_retry_after": true,
"max_retry_after_ms": 30000,
"jitter": true,
"route_weights": {}
},
"guardrail_webhook_url": "<string>",
"guardrail_webhook_secret_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"guardrail_webhook_mode": "off",
"guardrail_webhook_timeout_ms": 5050,
"guardrail_webhook_failure_action": "fail_open",
"created_by": "<string>"
},
"meta": {
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}/v1 operation above is the one to write against — it is what the five SDKs,
the Postman collection and this spec cover. Most of an agent’s configuration is
applied after creation with
Update agent; see
the agent object for the full
field list.
The dashboard twin
Create agent
POST /admin/ai-gateway/{gatewayId}/agents
admin.knoxcall.com), where the dashboard calls it. Authenticate with a session JWT, send the X-Tenant-ID header, and use an owner or admin account — it is not an api.knoxcall.com API-key endpoint. The fields below describe that endpoint.
Request body
{
"name": "Customer Support Bot",
"slug": "support-bot",
"provider": "anthropic",
"upstream_secret_id": "00000000-0000-0000-0000-000000000000",
"description": "Handles tier-1 support questions",
"default_model": "claude-sonnet-4-6",
"budget_daily_usd": 10.00,
"budget_monthly_usd": 200.00,
"streaming_enabled": true
}
| Field | Type | Description |
|---|---|---|
name | string | Required. Display name for the agent. |
slug | string | Required. URL slug; the agent is served at /v1/ai/{slug}. |
provider | string | Required (unless you pass primary_route_id). Any id in the server-side catalog — fourteen at the time of writing: anthropic, openai, gemini, cohere, azure-openai, ollama, groq, together, mistral, deepseek, fireworks, xai, bedrock, openai-compatible. Determines the upstream URL and the auth header template. An unknown value returns a 400 naming the valid set, so read that rather than mirroring this list in your own code. |
upstream | string (url) | Required for the four providers whose endpoint is yours, not the vendor’s: azure-openai, ollama, bedrock, openai-compatible. Resolved and SSRF-checked at write time for bedrock and openai-compatible — a private, loopback or link-local address is a 400, because the request would carry your decrypted provider key there. |
upstream_secret_id | string (uuid) | Required. ID of a secret in this tenant holding the provider API key. The gateway auto-creates an upstream route (ai-gateway-{slug}) wired to this secret. |
description | string | Optional free-text description. |
default_model | string | Optional. Defaults to that provider’s pricebook default. Required for openai-compatible, which has no default because the endpoint is yours. |
budget_daily_usd | number | Optional daily spend cap. |
budget_monthly_usd | number | Optional monthly spend cap. |
streaming_enabled | boolean | Optional. Defaults to true. |
provider + upstream_secret_id; you do not pass primary_route_id at create time. Model policy (model_allowlist, model_denylist, model_rewrite), tool_allowlist, output_schema / output_validation_action, fallback_route_ids, and firewall_policy_id are configured after creation via PATCH /admin/ai-gateway/{gatewayId}/agents/{agentId} — they are not read by the create handler.Authorizations
OAuth 2.1 authentication — recommended for new integrations. Access tokens
(kc_ prefix) are minted at the root-host token endpoint
https://api.knoxcall.com/oauth/token and passed as Authorization: Bearer <access_token>.
Public clients must use PKCE with the authorization_code grant; confidential
clients may use client_credentials. The first-party SDKs and the
knoxcall login CLI handle token minting, caching, refresh, and DPoP for you.
Path Parameters
The parent AI gateway UUID.
Body
The route the agent's upstream calls are proxied through. Pass this OR provider + upstream_secret_id, never both.
Name a provider and KnoxCall composes the upstream route for you, injecting the credential from upstream_secret_id. Requires upstream_secret_id, and forbids primary_route_id.
Ten of these speak the OpenAI Chat Completions API (openai, azure-openai, ollama, groq, together, mistral, deepseek, fireworks, xai, openai-compatible), so an OpenAI SDK works unchanged — point it at the agent URL plus the provider's OpenAI sub-path (/v1, or /openai/v1 for azure-openai and groq).
bedrock is different: it has no static credential header. The secret's value is a JSON AWS credential ({"accessKeyId":"…","secretAccessKey":"…","sessionToken":"…"}) and KnoxCall computes the SigV4 signature server-side over the finalized request, so the AWS secret access key is never transmitted.
anthropic, openai, gemini, cohere, azure-openai, ollama, groq, together, mistral, deepseek, fireworks, xai, bedrock, openai-compatible, null The secret holding your provider credential. Required whenever provider is set.
Base URL of the upstream, required for the providers whose endpoint is yours rather than the vendor's: azure-openai, ollama, bedrock, openai-compatible.
For bedrock and openai-compatible this URL is resolved and SSRF-checked before any row is written — a private, loopback or link-local address is refused with a 400, because the request would carry your decrypted provider credential to that destination. ollama is deliberately exempt: pointing at your own box on a private network is what that provider is for.
Defaults to the provider's own default. Required when provider is openai-compatible — KnoxCall cannot know which model your endpoint serves, and reports that agent's cost as unpriced.
How this agent retries and spreads load (AIGW-42). An empty object — the default for every agent — means one attempt per route and fail-over to fallback_route_ids on 5xx only, which is the behaviour the gateway has always had.
Every field is clamped server-side. A request parked waiting to retry occupies an API worker, so max_attempts caps at 5, any single delay at 30s, a honoured Retry-After at 60s, and the total sleep for one request at 60s regardless of how the fields combine.
Show child attributes
Show child attributes
HTTPS endpoint your own scanner listens on (AIGW-45). Every prompt — and, in response/both mode, every response — is POSTed to it for a verdict before it moves.
What it receives is the REDACTED, post-tokenization body: the same bytes the provider would get, never a resolved provider credential and never your KnoxCall token. Shipping raw PHI to a third party to ask whether it contains PHI would be the leak the gateway exists to prevent.
The destination is resolved and refused at write time AND on every call — a private, loopback, link-local or cloud-metadata address is a 400, because DNS is not a promise.
Secret holding the HMAC-SHA256 key deliveries are signed with. The signature covers <timestamp>.<body> and travels in x-knox-guardrail-signature with the timestamp in x-knox-guardrail-timestamp, so a captured delivery is not replayable. Omit to send unsigned — the receiver then cannot prove the call came from KnoxCall.
Which directions are inspected. response and both evaluate the complete buffered response before it is sent, so an agent in those modes REFUSES a streaming request (400 guardrail_streaming_unsupported) rather than serving one past a control that could not run on it.
off, request, response, both How long to wait for a verdict. Clamped, because a request waiting on your scanner is holding one of the gateway's API workers.
100 <= x <= 10000What happens when the hook does not answer. Applies identically to EVERY non-answer — timeout, refused connection, refused destination, non-2xx, unparseable body, an action verb we do not recognise. Defaults to fail_open, which is the posture an agent already has with no hook at all.
A hook responds 200 with {"action":"allow"|"block"|"flag","reason":"…"}. block refuses the request with 400 guardrail_block and your reason in the message; flag serves it and records the outcome.
fail_open, fail_closed Map of requested model → substituted model. Values must be STRINGS (the server validates with a string map) and at most 100 entries.
Show child attributes
Show child attributes
What happens once a budget is exhausted. fallback needs fallback_agent_id; without one it behaves as block.
block, warn, fallback AIGW-100 legacy alias for pii_response_mode (true -> detokenize, false -> redact). Still accepted; sending both with contradictory values is a 400.
What happens to the PROMPT before it leaves KnoxCall. Default tokenize. Set off only for an agent whose payload must reach the provider byte-for-byte (structured tool-use JSON, for example) — it means prompts are forwarded unscanned.
off, tokenize What happens to the provider's answer. Default detokenize.
redact, detokenize How many characters of a STREAMED answer are held back while a detector decides. Larger catches an entity split across more chunks; smaller is lower latency.
How a streamed answer is rewritten. holdback releases text behind a sliding window; buffer withholds the whole answer until it is complete; monitor REPORTS detections without rewriting, so the raw value reaches the client — an observability mode, not a redaction one.
holdback, buffer, monitor FinOps attribution labels (cost_center/team/project/…) echoed onto this agent's usage rows.
semantic additionally needs cache_embedding_model; without one the cache reports itself degraded and serves nothing.
off, exact, semantic Cosine floor for a semantic cache hit, 0–1. Lower means more hits, and more wrong ones. NOT nullable: the column is NOT NULL and the validator is non-nullable, so an explicit null is a 400.
0 <= x <= 1JSON Schema the answer is validated against.
block, retry, warn Was this page helpful?
curl --request POST \
--url https://api.knoxcall.com/v1/ai-gateway/gateways/{gatewayId}/agents \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Support Bot",
"slug": "support-bot",
"default_model": "claude-sonnet-5"
}
'import requests
url = "https://api.knoxcall.com/v1/ai-gateway/gateways/{gatewayId}/agents"
payload = {
"name": "Support Bot",
"slug": "support-bot",
"default_model": "claude-sonnet-5"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({name: 'Support Bot', slug: 'support-bot', default_model: 'claude-sonnet-5'})
};
fetch('https://api.knoxcall.com/v1/ai-gateway/gateways/{gatewayId}/agents', 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.knoxcall.com/v1/ai-gateway/gateways/{gatewayId}/agents",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Support Bot',
'slug' => 'support-bot',
'default_model' => 'claude-sonnet-5'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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.knoxcall.com/v1/ai-gateway/gateways/{gatewayId}/agents"
payload := strings.NewReader("{\n \"name\": \"Support Bot\",\n \"slug\": \"support-bot\",\n \"default_model\": \"claude-sonnet-5\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.post("https://api.knoxcall.com/v1/ai-gateway/gateways/{gatewayId}/agents")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Support Bot\",\n \"slug\": \"support-bot\",\n \"default_model\": \"claude-sonnet-5\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.knoxcall.com/v1/ai-gateway/gateways/{gatewayId}/agents")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Support Bot\",\n \"slug\": \"support-bot\",\n \"default_model\": \"claude-sonnet-5\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"gateway_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"slug": "<string>",
"status": "active",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"agent_url": "<string>",
"tenant_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"description": "<string>",
"primary_route_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"fallback_route_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"model_allowlist": [
"<string>"
],
"model_denylist": [
"<string>"
],
"default_model": "<string>",
"model_rewrite": {},
"budget_daily_usd": 123,
"budget_monthly_usd": 123,
"budget_per_call_max_tokens": 123,
"budget_overage_action": "<string>",
"fallback_agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"pii_redact_policy_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"pii_detokenize_response": true,
"pii_request_mode": "off",
"pii_response_mode": "redact",
"pii_streaming_holdback_chars": 123,
"pii_streaming_mode": "<string>",
"cache_mode": "<string>",
"cache_ttl_seconds": 123,
"cache_similarity_threshold": 123,
"cache_embedding_model": "<string>",
"streaming_enabled": true,
"firewall_policy_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"tool_allowlist": [
"<string>"
],
"output_schema": {},
"output_validation_action": "<string>",
"data_residency_region": "us",
"cmek_key_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"paused_reason": "<string>",
"tags": {},
"provider": "<string>",
"routing_policy": {
"retry_on": [
"429"
],
"max_attempts": 3,
"backoff_ms": 15000,
"backoff_multiplier": 5.5,
"max_backoff_ms": 15000,
"respect_retry_after": true,
"max_retry_after_ms": 30000,
"jitter": true,
"route_weights": {}
},
"guardrail_webhook_url": "<string>",
"guardrail_webhook_secret_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"guardrail_webhook_mode": "off",
"guardrail_webhook_timeout_ms": 5050,
"guardrail_webhook_failure_action": "fail_open",
"created_by": "<string>"
},
"meta": {
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}