Save (create or edit) a feed connection's credential. See
curl --request PUT \ --url http://localhost:3001/admin/feed-connections/string/credentials \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{}'import requests
url = "http://localhost:3001/admin/feed-connections/string/credentials"
payload = {}headers = { "Authorization": "Bearer <token>", "Content-Type": "application/json"}
response = requests.put(url, json=payload, headers=headers)
print(response.json())use std::str::FromStr;use serde_json::json;use reqwest;
#[tokio::main]pub async fn main() { let url = "http://localhost:3001/admin/feed-connections/string/credentials";
let payload = json!({});
let mut headers = reqwest::header::HeaderMap::new(); headers.insert("Authorization", "Bearer <token>".parse().unwrap()); headers.insert("Content-Type", "application/json".parse().unwrap());
let client = reqwest::Client::new(); let response = client.request(reqwest::Method::from_str("PUT").unwrap(), url) .headers(headers) .json(&payload) .send() .await;
let results = response.unwrap() .json::<serde_json::Value>() .await .unwrap();
dbg!(results);}put_broker_connection_credentials for the order this follows and why —
the only differences are parse_feed/test_feed in place of
parse_broker/test_broker (feeds have no per-connection environment to
thread through) and save_feed in place of save_broker.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Feed connection code
Request Bodyrequired
Section titled “Request Bodyrequired”A submitted credential form: field name to raw string value, exactly as an HTML form or a JSON object would hand it over. Untyped on purpose — giving each broker its own typed request struct would just move the “which fields did they actually send” question from here into the handler, where the merge rule would have to be reimplemented per broker.
#[serde(flatten)] so the wire body is the field map itself
({"host": "...", "port": "4101"}), not {"fields": {...}} — the shape
the cockpit form naturally produces.
object
Example generated
{ "additionalProperty": "example"}Responses
Section titled “Responses”Saved, tested where possible, and reloaded
Response body for a successful credential save.
reload is this connection’s own entry out of the reload report the save
already triggered — not a second call the cockpit has to make — so the UI
can say “applied” (a REST adapter, swapped live) or “restart required” (a
FIX session) in the same response that confirms the write.
object
Present exactly when tested is false: TestOutcome::NotTestable’s
own explanation (e.g. “FIX credentials are validated at session
logon, not before save”), passed through so the cockpit renders the
server’s reason instead of hardcoding its own guess at the wording.
Always None when tested is true — a Failed test never reaches
this response at all (422, before anything is written).
The credential view safe to put on the wire. state mirrors
CredentialState so the cockpit can tell “needs setup” (unconfigured)
apart from “stored but the master key does not open it” (error) — the
distinction the whole credential store was built to preserve; collapsing
them would invite an operator to re-enter a credential that is already
there instead of fixing the key. fields is populated only when state
is "configured"; message only when state is "error". Never carries
a decrypted secret, in either state.
object
One field of a credential as it may be shown on the wire. Named so the UI
always knows the field exists — even when value is withheld — and can
render a “leave blank to keep” input rather than an empty box that looks
like the value was lost. secret is carried explicitly rather than left
for a caller to infer from value.is_none(), so a future field that is
merely absent (not secret) cannot be mistaken for one that is withheld.
object
A display-only preview of a withheld value (see masked). Never a
value the client may send back.
The stored value, when it is safe to echo and safe to submit back
unchanged. None for both secrets and masked identifiers — in either
case the cockpit must leave the input blank, so that an untouched
field submits empty and the merge rule keeps what is stored.
The reason a stored blob could not be used — set only when
state == "error", and always a description, never the payload.
“configured” | “unconfigured” | “error”
None only if the reload report (queried by this same connection’s
code, right after the save that triggered it) somehow lacks an entry
for it — never expected in practice, but a missing key is safer to
surface as “unknown” than to synthesize an outcome for.
object
Whether the credential was actually checked against the broker before
being written — false for FIX (see credentials_api::test_broker),
so the UI reports “not testable” rather than implying a pass it never
earned.
Example generated
{ "message": "example", "redacted": { "code": "example", "fields": [ { "hint": "example", "name": "example", "secret": true, "value": "example" } ], "message": "example", "state": "example", "updated_at": "2026-04-15T12:00:00Z" }, "reload": {}, "tested": true}The request body was malformed, or could not be parsed into a credential (unknown field, …)
Not found
The credential did not authenticate; nothing was written
The credential store could not be read, the master key is invalid or unset, or the reload failed