Provider credentials
6 endpoints. Every path below is relative to the API base URL and already includes the /api/v1 prefix — see conventions for authentication, pagination, errors and idempotency.
| Endpoint | ||
|---|---|---|
| GET | /credentials | List provider credentials. Secret values are absent; secret_keys says which fields are set |
| POST | /credentials | Store a provider credential. The secret is encrypted before it hits disk |
| GET | /credentials/{credential} | Retrieve one provider credential; stored secret values are never returned |
| PUT | /credentials/{credential} | Update metadata and/or rotate individual secret fields |
| DELETE | /credentials/{credential} | Soft-delete a provider credential and return an empty body |
| POST | /credentials/{credential}/test | Validate a stored credential by making a real read-only call to the provider and return the verdict |
Endpoints
List provider credentials. Secret values are absent; secret_keys says which fields are set
- Requires
credential.read. - Filters are class, provider and active_only.
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
active_only | boolean | — | default false |
curl -X GET "$CALLAGENT_API_URL/credentials?active_only=false" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Accept: application/json"const response = await fetch(`${process.env.CALLAGENT_API_URL}/credentials?active_only=false`, {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
},
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/credentials?active_only=false');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
],
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.get(
f"{os.environ['CALLAGENT_API_URL']}/credentials?active_only=false",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | Array of ProviderCredentialResource |
Errors
| Status | When it happens |
|---|---|
403 | The presenting API key does not carry the credential.read scope |
Example response, generated from the schema:
{
"data": [
{
"id": "01JQ0000000000000000000000",
"object": "string",
"class": "string",
"provider": "string",
"name": "Support Line",
"description": "string",
"config": {},
"secret_keys": {},
"is_default": true,
"active": true,
"validation_status": "string",
"validated_at": "2026-01-01T12:00:00+00:00",
"validation_error": "string",
"capabilities": {},
"expires_at": "2026-01-01T12:00:00+00:00",
"rotated_at": "2026-01-01T12:00:00+00:00",
"created_at": "2026-01-01T12:00:00+00:00"
}
]
}Store a provider credential. The secret is encrypted before it hits disk
- Requires
credential.write. secretsis required and every value is cast to a string.classis a closed set: telephony, model, transcriber, voice, storage, observability, integration, mcp, oauth, sip.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
class | telephony · model · transcriber · voice · storage · observability … | yes | |
provider | string | yes | max 64 chars |
name | string | yes | max 255 chars |
description | string | null | — | max 500 chars |
secrets | string[] | yes | min 1 items |
config | string[] | null | — | |
is_default | boolean | null | — |
curl -X POST "$CALLAGENT_API_URL/credentials" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"class": "telephony",
"provider": "string",
"name": "Support Line",
"secrets": [
"string"
],
"is_default": true
}'const response = await fetch(`${process.env.CALLAGENT_API_URL}/credentials`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"class": "telephony",
"provider": "string",
"name": "Support Line",
"secrets": [
"string"
],
"is_default": true
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/credentials');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'class' => 'telephony',
'provider' => 'string',
'name' => 'Support Line',
'secrets' => [
'string',
],
'is_default' => true,
]),
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.post(
f"{os.environ['CALLAGENT_API_URL']}/credentials",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
json={
"class": "telephony",
"provider": "string",
"name": "Support Line",
"secrets": [
"string"
],
"is_default": True
},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
201 | data-wrapped object |
Errors
| Status | When it happens |
|---|---|
403 | The presenting API key does not carry the credential.write scope |
422 | Validation failed on class, provider, name or secrets |
Example response, generated from the schema:
{
"data": {
"id": "01JQ0000000000000000000000",
"object": "string",
"class": "string",
"provider": "string",
"name": "Support Line",
"description": "string",
"config": {},
"secret_keys": {},
"is_default": true,
"active": true,
"validation_status": "string",
"validated_at": "2026-01-01T12:00:00+00:00",
"validation_error": "string",
"capabilities": {},
"expires_at": "2026-01-01T12:00:00+00:00",
"rotated_at": "2026-01-01T12:00:00+00:00",
"created_at": "2026-01-01T12:00:00+00:00"
}
}Retrieve one provider credential; stored secret values are never returned
- Requires
credential.read.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
credential | string | yes | The credential ID |
curl -X GET "$CALLAGENT_API_URL/credentials/01JQ0000000000000000000000" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Accept: application/json"const response = await fetch(`${process.env.CALLAGENT_API_URL}/credentials/01JQ0000000000000000000000`, {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
},
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/credentials/01JQ0000000000000000000000');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
],
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.get(
f"{os.environ['CALLAGENT_API_URL']}/credentials/01JQ0000000000000000000000",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | ProviderCredentialResource |
Errors
| Status | When it happens |
|---|---|
403 | The presenting API key does not carry the credential.read scope |
404 | No such credential in this organization |
Example response, generated from the schema:
{
"data": {
"id": "01JQ0000000000000000000000",
"object": "string",
"class": "string",
"provider": "string",
"name": "Support Line",
"description": "string",
"config": {},
"secret_keys": {},
"is_default": true,
"active": true,
"validation_status": "string",
"validated_at": "2026-01-01T12:00:00+00:00",
"validation_error": "string",
"capabilities": {},
"expires_at": "2026-01-01T12:00:00+00:00",
"rotated_at": "2026-01-01T12:00:00+00:00",
"created_at": "2026-01-01T12:00:00+00:00"
}
}Update metadata and/or rotate individual secret fields
- Requires
credential.write. PATCHis accepted on the same path, with the same body.secretsmerges into the stored set rather than replacing it; sending it also stamps rotated_at and resets validation_status to unknown.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
credential | string | yes | The credential ID |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | — | max 255 chars |
description | string | null | — | max 500 chars |
config | string[] | null | — | |
secrets | string[] | — | |
is_default | boolean | — | |
active | boolean | — |
curl -X PUT "$CALLAGENT_API_URL/credentials/01JQ0000000000000000000000" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "Support Line",
"is_default": true,
"active": true
}'const response = await fetch(`${process.env.CALLAGENT_API_URL}/credentials/01JQ0000000000000000000000`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Support Line",
"is_default": true,
"active": true
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/credentials/01JQ0000000000000000000000');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Support Line',
'is_default' => true,
'active' => true,
]),
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.put(
f"{os.environ['CALLAGENT_API_URL']}/credentials/01JQ0000000000000000000000",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
json={
"name": "Support Line",
"is_default": True,
"active": True
},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | ProviderCredentialResource |
Errors
| Status | When it happens |
|---|---|
403 | The presenting API key does not carry the credential.write scope |
404 | No such credential in this organization |
422 | Validation failed on name, description, config, secrets, is_default or active |
Example response, generated from the schema:
{
"data": {
"id": "01JQ0000000000000000000000",
"object": "string",
"class": "string",
"provider": "string",
"name": "Support Line",
"description": "string",
"config": {},
"secret_keys": {},
"is_default": true,
"active": true,
"validation_status": "string",
"validated_at": "2026-01-01T12:00:00+00:00",
"validation_error": "string",
"capabilities": {},
"expires_at": "2026-01-01T12:00:00+00:00",
"rotated_at": "2026-01-01T12:00:00+00:00",
"created_at": "2026-01-01T12:00:00+00:00"
}
}Soft-delete a provider credential and return an empty body
- Requires
credential.write. - This response is not
data-wrapped. - This is a soft delete and it does not wipe the stored ciphertext; only the integrations disconnect path clears secrets before deleting.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
credential | string | yes | The credential ID |
curl -X DELETE "$CALLAGENT_API_URL/credentials/01JQ0000000000000000000000" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Accept: application/json"const response = await fetch(`${process.env.CALLAGENT_API_URL}/credentials/01JQ0000000000000000000000`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
},
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/credentials/01JQ0000000000000000000000');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
],
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.delete(
f"{os.environ['CALLAGENT_API_URL']}/credentials/01JQ0000000000000000000000",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
204 | No content |
Errors
| Status | When it happens |
|---|---|
403 | The presenting API key does not carry the credential.write scope |
404 | No such credential in this organization |
Validate a stored credential by making a real read-only call to the provider and return the verdict
Delegates to CredentialValidator, which makes a real read-only call to the provider. A provider the registry has no probe for comes back as verified: false — a clearly-labelled "cannot verify" rather than a green tick it has not earned (§188).
- Requires
credential.write. - Always 200 — the verdict is the body. verified:false means no probe exists or a field is missing, not that the credential failed. Requires credential.write despite being read-only.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
credential | string | yes | The credential ID |
curl -X POST "$CALLAGENT_API_URL/credentials/01JQ0000000000000000000000/test" \
-H "Authorization: Bearer $CALLAGENT_API_KEY" \
-H "Accept: application/json"const response = await fetch(`${process.env.CALLAGENT_API_URL}/credentials/01JQ0000000000000000000000/test`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
Accept: 'application/json',
},
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.type}: ${error.message}`);
}
const { data } = await response.json();<?php
$handle = curl_init(getenv('CALLAGENT_API_URL') . '/credentials/01JQ0000000000000000000000/test');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
'Accept: application/json',
],
]);
$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
if ($status >= 400) {
throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}
$data = $body['data'];import os, requests
response = requests.post(
f"{os.environ['CALLAGENT_API_URL']}/credentials/01JQ0000000000000000000000/test",
headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]Responses
| Status | Body |
|---|---|
200 | 200 either way: the request succeeded, and the credential's verdict is the payload. A 4xx here would be indistinguishable from the caller having got the request wrong. |
Errors
| Status | When it happens |
|---|---|
403 | The presenting API key does not carry the credential.write scope |
404 | No such credential in this organization |
Example response, generated from the schema:
{
"data": "string"
}