Provider Credentials
Quick example
- curl
- Python
- Node.js
# Vault an OpenAI key
curl "$TRINITITE_BASE/v1/provider-credentials" \
-H "Authorization: Bearer $TRINITITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "openai",
"name": "openai-prod",
"secret": "sk-prod-XXX",
"model_allowlist": ["gpt-4o", "gpt-4o-mini"]
}'
import os, requests
requests.post(f"{os.environ['TRINITITE_BASE']}/v1/provider-credentials",
headers={"Authorization": f"Bearer {os.environ['TRINITITE_API_KEY']}"},
json={"provider": "openai", "name": "openai-prod",
"secret": os.environ["OPENAI_KEY"], "model_allowlist": ["gpt-4o", "gpt-4o-mini"]})
await fetch(`${process.env.TRINITITE_BASE}/v1/provider-credentials`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.TRINITITE_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
provider: 'openai', name: 'openai-prod', secret: process.env.OPENAI_KEY,
model_allowlist: ['gpt-4o', 'gpt-4o-mini'],
}),
});
The raw secret is returned only once at creation. After that, only the redacted ID is accessible.
Overview
Provider Credentials store upstream LLM API keys (OpenAI, Anthropic, Azure OpenAI, custom) encrypted at rest. They're referenced from proxy calls via the X-Trinitite-Credential-Id header — your applications never handle raw provider keys.
Authentication: Authorization: Bearer <session_token | api_key> with the relevant provider_credentials:* permission.
Endpoints
| Method | Path | Permission |
|---|---|---|
GET | /v1/proxy/credentials | provider_credentials:read |
POST | /v1/proxy/credentials | provider_credentials:create |
GET | /v1/proxy/credentials/{id} | provider_credentials:read |
PATCH | /v1/proxy/credentials/{id} | provider_credentials:create |
DELETE | /v1/proxy/credentials/{id} | provider_credentials:delete |
Org-wide spend caps and rate limits per credential — see Governance Controls.
Credential object
{
"credential_id": "cred_01JF8RCRD1A2B3C4D5E6F7G8H9I",
"provider": "openai",
"label": "production-openai-primary",
"key_prefix": "sk-proj-...",
"key_suffix": "...d8f9",
"base_url": null,
"allowed_models": ["gpt-4o", "gpt-4o-mini", "o3-mini"],
"status": "active",
"created_at": "2026-04-01T00:00:00Z",
"last_used_at": "2026-05-01T15:42:00Z",
"monthly_spend_cap_usd": "5000.00",
"rpm_limit": 600,
"disabled": false
}
| Field | Description |
|---|---|
credential_id | Stable ID (cred_*). Used in X-Trinitite-Credential-Id |
provider | openai, anthropic, azure_openai, custom |
label | Human-readable label |
key_prefix / key_suffix | Truncated bookends for identification — the raw key is never returned after creation |
base_url | Override base URL (for Azure deployments or self-hosted custom endpoints) |
allowed_models | If set, only these model names are permitted via this credential |
monthly_spend_cap_usd | Decimal string (USD); null means unbounded |
rpm_limit | Per-credential requests-per-minute cap |
POST /v1/proxy/credentials
Store a new credential.
{
"provider": "openai",
"label": "production-openai-primary",
"plaintext_key": "sk-proj-4f8d9e2a1c6b7f3a9e1d2c4b5a6f7e8d",
"allowed_models": ["gpt-4o", "gpt-4o-mini", "o3-mini"]
}
| Field | Type | Required | Description |
|---|---|---|---|
provider | enum | Yes | openai, anthropic, azure_openai, custom |
label | string | Yes | 1–100 chars; unique within your org |
plaintext_key | string | Yes | Raw API key. Encrypted before storage; not returned on subsequent reads |
base_url | string | No | Override base URL — required for azure_openai and custom |
allowed_models | string[] | No | If set, only listed models are permitted |
Response — 201 Created returns the credential object including plaintext_key once. Store it in your secrets manager — subsequent reads redact it.
GET /v1/proxy/credentials
Returns the redacted list (no plaintext_key).
| Query | Type | Description |
|---|---|---|
provider | string | Filter by provider |
status | string | active or revoked |
limit / cursor | — | Cursor pagination |
{
"data": [
{ "credential_id": "cred_01JF...", "provider": "openai", "label": "production-openai-primary", "status": "active", ... }
],
"page": { "next_cursor": null, "has_more": false }
}
GET /v1/proxy/credentials/{id}
Returns one credential, redacted.
PATCH /v1/proxy/credentials/{id}
Partial update. Use plaintext_key to rotate the underlying provider key — the old key is destroyed atomically and in-flight calls switch over with no application changes.
{
"label": "production-openai-primary-rotated-2026-05",
"plaintext_key": "sk-proj-NEWKEYHERE",
"allowed_models": ["gpt-4o", "gpt-4o-mini", "o3-mini", "gpt-5"]
}
| Field | Description |
|---|---|
label | New label |
plaintext_key | New key (rotation). Returned once in the response |
base_url | New base URL |
allowed_models | Replace allowlist |
DELETE /v1/proxy/credentials/{id}
Revoke. In-flight proxy calls using the credential start failing with 404 credential_not_found. Returns 204 No Content.
Errors
| HTTP | error.code | Cause |
|---|---|---|
400 | validation_error | Missing required field; invalid provider; base_url required for custom |
401 | unauthenticated | Missing or invalid credential |
403 | forbidden | Caller lacks the required scope |
404 | not_found / credential_not_found | Credential does not exist or is revoked |
409 | conflict | A credential with the same label already exists |
Best practices
- Rotate quarterly. Use
PATCHwithplaintext_key— clients pick up the new key on the next request without redeploys. - Scope by environment. Issue separate credentials for
dev,staging,production. Combine with API key environment scoping for blast-radius isolation. - Lock down
allowed_models. Restricting a credential to known-good models prevents accidental usage of expensive or unapproved tiers. - Cap spend.
monthly_spend_cap_usdis a hard limit — Trinitite trips the credential into a soft-fail state once tripped (see Governance Controls).
Next steps
- Use the credential on a proxy call → Proxy
- Monitor per-credential spend → Governance Controls
- Inspect every call against this credential → Logs API