curl --request PUT \
--url https://{host}/api/llm-gateway/admin/providers/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"api_key": "<string>",
"auth_type": "<string>",
"base_url": "<string>",
"billing_note": "<string>",
"change_note": "<string>",
"connection_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"credentials": "<unknown>",
"enabled": true,
"enforce_health_check": true,
"name": "<string>",
"request_timeout_secs": 123,
"settings": "<unknown>",
"stream_idle_timeout_secs": 123
}
'import requests
url = "https://{host}/api/llm-gateway/admin/providers/{id}"
payload = {
"api_key": "<string>",
"auth_type": "<string>",
"base_url": "<string>",
"billing_note": "<string>",
"change_note": "<string>",
"connection_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"credentials": "<unknown>",
"enabled": True,
"enforce_health_check": True,
"name": "<string>",
"request_timeout_secs": 123,
"settings": "<unknown>",
"stream_idle_timeout_secs": 123
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
api_key: '<string>',
auth_type: '<string>',
base_url: '<string>',
billing_note: '<string>',
change_note: '<string>',
connection_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
credentials: '<unknown>',
enabled: true,
enforce_health_check: true,
name: '<string>',
request_timeout_secs: 123,
settings: '<unknown>',
stream_idle_timeout_secs: 123
})
};
fetch('https://{host}/api/llm-gateway/admin/providers/{id}', 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://{host}/api/llm-gateway/admin/providers/{id}",
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>',
'auth_type' => '<string>',
'base_url' => '<string>',
'billing_note' => '<string>',
'change_note' => '<string>',
'connection_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'credentials' => '<unknown>',
'enabled' => true,
'enforce_health_check' => true,
'name' => '<string>',
'request_timeout_secs' => 123,
'settings' => '<unknown>',
'stream_idle_timeout_secs' => 123
]),
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://{host}/api/llm-gateway/admin/providers/{id}"
payload := strings.NewReader("{\n \"api_key\": \"<string>\",\n \"auth_type\": \"<string>\",\n \"base_url\": \"<string>\",\n \"billing_note\": \"<string>\",\n \"change_note\": \"<string>\",\n \"connection_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"credentials\": \"<unknown>\",\n \"enabled\": true,\n \"enforce_health_check\": true,\n \"name\": \"<string>\",\n \"request_timeout_secs\": 123,\n \"settings\": \"<unknown>\",\n \"stream_idle_timeout_secs\": 123\n}")
req, _ := http.NewRequest("PUT", 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.put("https://{host}/api/llm-gateway/admin/providers/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"api_key\": \"<string>\",\n \"auth_type\": \"<string>\",\n \"base_url\": \"<string>\",\n \"billing_note\": \"<string>\",\n \"change_note\": \"<string>\",\n \"connection_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"credentials\": \"<unknown>\",\n \"enabled\": true,\n \"enforce_health_check\": true,\n \"name\": \"<string>\",\n \"request_timeout_secs\": 123,\n \"settings\": \"<unknown>\",\n \"stream_idle_timeout_secs\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://{host}/api/llm-gateway/admin/providers/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"api_key\": \"<string>\",\n \"auth_type\": \"<string>\",\n \"base_url\": \"<string>\",\n \"billing_note\": \"<string>\",\n \"change_note\": \"<string>\",\n \"connection_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"credentials\": \"<unknown>\",\n \"enabled\": true,\n \"enforce_health_check\": true,\n \"name\": \"<string>\",\n \"request_timeout_secs\": 123,\n \"settings\": \"<unknown>\",\n \"stream_idle_timeout_secs\": 123\n}"
response = http.request(request)
puts response.read_body{
"auth_type": "<string>",
"base_url": "<string>",
"billing_mode": "per_token",
"created_at": "2023-11-07T05:31:56Z",
"enabled": true,
"enforce_health_check": true,
"health_status": "<string>",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"model_provider": "<string>",
"model_sync_mode": "off",
"name": "<string>",
"org_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"secret_path": "<string>",
"settings": "<unknown>",
"updated_at": "2023-11-07T05:31:56Z",
"created_by_email": "<string>",
"created_by_name": "<string>",
"created_by_user_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"last_change_note": "<string>",
"last_modified_at": "2023-11-07T05:31:56Z",
"last_modified_by_email": "<string>",
"last_modified_by_name": "<string>",
"last_modified_by_user_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"billing_note": "<string>",
"billing_reason": "subscription",
"catalog_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"catalog_slug": "<string>",
"connection_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"health_checked_at": "2023-11-07T05:31:56Z",
"health_detail": "<string>",
"request_timeout_secs": 123,
"stream_idle_timeout_secs": 123
}Update a provider
curl --request PUT \
--url https://{host}/api/llm-gateway/admin/providers/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"api_key": "<string>",
"auth_type": "<string>",
"base_url": "<string>",
"billing_note": "<string>",
"change_note": "<string>",
"connection_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"credentials": "<unknown>",
"enabled": true,
"enforce_health_check": true,
"name": "<string>",
"request_timeout_secs": 123,
"settings": "<unknown>",
"stream_idle_timeout_secs": 123
}
'import requests
url = "https://{host}/api/llm-gateway/admin/providers/{id}"
payload = {
"api_key": "<string>",
"auth_type": "<string>",
"base_url": "<string>",
"billing_note": "<string>",
"change_note": "<string>",
"connection_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"credentials": "<unknown>",
"enabled": True,
"enforce_health_check": True,
"name": "<string>",
"request_timeout_secs": 123,
"settings": "<unknown>",
"stream_idle_timeout_secs": 123
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
api_key: '<string>',
auth_type: '<string>',
base_url: '<string>',
billing_note: '<string>',
change_note: '<string>',
connection_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
credentials: '<unknown>',
enabled: true,
enforce_health_check: true,
name: '<string>',
request_timeout_secs: 123,
settings: '<unknown>',
stream_idle_timeout_secs: 123
})
};
fetch('https://{host}/api/llm-gateway/admin/providers/{id}', 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://{host}/api/llm-gateway/admin/providers/{id}",
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>',
'auth_type' => '<string>',
'base_url' => '<string>',
'billing_note' => '<string>',
'change_note' => '<string>',
'connection_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'credentials' => '<unknown>',
'enabled' => true,
'enforce_health_check' => true,
'name' => '<string>',
'request_timeout_secs' => 123,
'settings' => '<unknown>',
'stream_idle_timeout_secs' => 123
]),
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://{host}/api/llm-gateway/admin/providers/{id}"
payload := strings.NewReader("{\n \"api_key\": \"<string>\",\n \"auth_type\": \"<string>\",\n \"base_url\": \"<string>\",\n \"billing_note\": \"<string>\",\n \"change_note\": \"<string>\",\n \"connection_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"credentials\": \"<unknown>\",\n \"enabled\": true,\n \"enforce_health_check\": true,\n \"name\": \"<string>\",\n \"request_timeout_secs\": 123,\n \"settings\": \"<unknown>\",\n \"stream_idle_timeout_secs\": 123\n}")
req, _ := http.NewRequest("PUT", 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.put("https://{host}/api/llm-gateway/admin/providers/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"api_key\": \"<string>\",\n \"auth_type\": \"<string>\",\n \"base_url\": \"<string>\",\n \"billing_note\": \"<string>\",\n \"change_note\": \"<string>\",\n \"connection_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"credentials\": \"<unknown>\",\n \"enabled\": true,\n \"enforce_health_check\": true,\n \"name\": \"<string>\",\n \"request_timeout_secs\": 123,\n \"settings\": \"<unknown>\",\n \"stream_idle_timeout_secs\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://{host}/api/llm-gateway/admin/providers/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"api_key\": \"<string>\",\n \"auth_type\": \"<string>\",\n \"base_url\": \"<string>\",\n \"billing_note\": \"<string>\",\n \"change_note\": \"<string>\",\n \"connection_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"credentials\": \"<unknown>\",\n \"enabled\": true,\n \"enforce_health_check\": true,\n \"name\": \"<string>\",\n \"request_timeout_secs\": 123,\n \"settings\": \"<unknown>\",\n \"stream_idle_timeout_secs\": 123\n}"
response = http.request(request)
puts response.read_body{
"auth_type": "<string>",
"base_url": "<string>",
"billing_mode": "per_token",
"created_at": "2023-11-07T05:31:56Z",
"enabled": true,
"enforce_health_check": true,
"health_status": "<string>",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"model_provider": "<string>",
"model_sync_mode": "off",
"name": "<string>",
"org_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"secret_path": "<string>",
"settings": "<unknown>",
"updated_at": "2023-11-07T05:31:56Z",
"created_by_email": "<string>",
"created_by_name": "<string>",
"created_by_user_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"last_change_note": "<string>",
"last_modified_at": "2023-11-07T05:31:56Z",
"last_modified_by_email": "<string>",
"last_modified_by_name": "<string>",
"last_modified_by_user_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"billing_note": "<string>",
"billing_reason": "subscription",
"catalog_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"catalog_slug": "<string>",
"connection_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"health_checked_at": "2023-11-07T05:31:56Z",
"health_detail": "<string>",
"request_timeout_secs": 123,
"stream_idle_timeout_secs": 123
}Authorizations
A Barndoor API key, sent as Authorization: Bearer <key>. Keys are prefixed bdai_ and are opaque — do not attempt to decode one. Interchangeable with an OAuth 2.0 access token.
Path Parameters
Identifier, a UUID
Body
None (omitted) leaves the billing mode unchanged (BCP-3876).
Changing this is not retroactive: cost is resolved at request time, so events already recorded keep the cost they were recorded with. The UI confirms that before saving.
per_token, not_metered None (omitted) leaves the note unchanged; Some(None) clears it.
None (omitted) leaves the reason unchanged; Some(None) (JSON
null) clears it, which is only valid while the provider is (or is
becoming) per_token. Deserialized tri-state for the same reason
connection_id is: serde would otherwise collapse null and missing,
so clearing a reason would be indistinguishable from not mentioning it.
subscription, local, external, other Optional free-text note explaining why this edit was made (BCP-3998).
Deliberately not tri-state like billing_note directly above, and
the contrast is the point: a billing note describes a standing
arrangement, so omitting it must leave it alone. A change note
describes one edit, so omitting it clears the previous one — otherwise
an unrelated later edit would inherit an explanation written for a
different change.
None (field omitted) leaves connection_id unchanged.
Some(None) (JSON null) clears the connection, switching the
provider back to per-provider direct credentials. Some(Some(id))
(re)binds the provider to a shared connection. Without the custom
deserializer below, serde would collapse the null and missing
cases to None, leaving the provider silently bound to its
previous shared credential even when the UI shows "Direct".
None (omitted) leaves the health-gate override unchanged.
Some(true) enforces the gate; Some(false) bypasses it so the
provider serves even when its connectivity probe is failing.
None (omitted) leaves the catalog-sync mode unchanged (BCP-3811).
off, additive, full None (omitted) leaves the column unchanged. Some(None) (JSON
null) clears the provider-tier override. Some(Some(secs)) sets it;
the range is fixed platform policy
(TOTAL_REQUEST_TIMEOUT_MIN_SECS..=TOTAL_REQUEST_TIMEOUT_MAX_SECS, see
[crate::proxy::timeouts]).
None (omitted) leaves the column unchanged. Some(None) (JSON
null) clears the provider-tier override. Some(Some(secs)) sets it;
the range is fixed platform policy
(STREAM_IDLE_TIMEOUT_MIN_SECS..=STREAM_IDLE_TIMEOUT_MAX_SECS, see
[crate::proxy::timeouts]).
Response
The requested record
Who created and last changed this provider, and why (BCP-3998).
Absent in the resolve/route queries -- the request path never reads it
-- and read with try_get, so those rows come back unattributed rather
than failing. That is indistinguishable from a row nobody has touched
since V75, which is the correct thing for a value the request path was
never given.
Whether the gateway records a per-token cost for this provider's
traffic (V69 / BCP-3876). PerToken for every provider that has not
opted in, which is the default and the back-compat value.
The request path does not read this field: billing consults
[ai_governance::CostCalculator]'s own cached not-metered set, which
is refreshed from the same query as pricing. This exists for the admin
API surface and the audit trail.
per_token, not_metered Operator override for the health gate (V46). When true (default)
routing requires health_status <> 'unhealthy'; when false the gate
is bypassed and the provider serves regardless of probe results (we
still probe and surface health). Lets an admin force traffic through a
false-positive probe.
Observed upstream reachability, recorded separately from enabled
(operator intent) so the system never overwrites the toggle (V45).
One of unverified | healthy | unhealthy. unverified is the
back-compat default and also covers provider families we don't probe
(Bedrock/Vertex/Azure, claude_oauth, codex_oauth). A provider serves traffic only
when enabled AND health_status <> 'unhealthy': a failed probe
auto-suspends it from routing (see resolve_*) without disabling it,
and it resumes once a probe records it healthy again.
Whether this provider tracks Barndoor's model catalog, and how far
(V63 / BCP-3811). Off for every provider that has not opted in,
which is the default and the back-compat value.
off, additive, full Optional free text explaining why the last change was made ("Raised for the Q3 batch job", "Vendor deprecated the old endpoint").
Admin-authored and rendered in the portal, so it is untrusted input: length-capped here, escaped on render.
When the change named by last_modified_by_* happened.
Its own column rather than the row's updated_at, because updated_at
is bumped by writes nobody is attributed for -- pairing the two would
date a named admin's edit to a background job's clock. See the V75
migration for the full reasoning.
Optional free-text context for the arrangement (max 200 chars). Human-readable only — never parsed, never aggregated.
How the vendor bills this provider. Required when billing_mode is
NotMetered, optional otherwise (V69 CHECK-enforced) — a subscription
billing overages is both per-token and a subscription. Purely
descriptive: no code branches on it, which is what lets a new reason be
added without touching the billing path.
subscription, local, external, other When the last connectivity probe ran (NULL until the first probe).
Reason for the last unhealthy or unverified probe (NULL if healthy or never probed).
Provider-tier total-request timeout override (non-streaming).
Operator-only and None by default: the effective value is written
at the route tier (model_mappings), so this only applies if a route
row somehow leaves it unset. Range
TOTAL_REQUEST_TIMEOUT_MIN_SECS..=TOTAL_REQUEST_TIMEOUT_MAX_SECS is
enforced by the CHECK constraint (see proxy::timeouts).
Provider-tier stream-idle timeout override (streaming). Operator-only
and None by default; the effective value is written at the route
tier (model_mappings). Range
STREAM_IDLE_TIMEOUT_MIN_SECS..=STREAM_IDLE_TIMEOUT_MAX_SECS is
enforced by the CHECK constraint (see proxy::timeouts).