Skip to content

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/credentialsList provider credentials. Secret values are absent; secret_keys says which fields are set
POST/credentialsStore 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}/testValidate 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 ​

GET/credentials
  • Requires credential.read.
  • Filters are class, provider and active_only.

Query parameters

NameTypeRequiredNotes
active_onlyboolean—default false
bash
curl -X GET "$CALLAGENT_API_URL/credentials?active_only=false" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
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
<?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'];
python
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

StatusBody
200Array of ProviderCredentialResource

Errors

StatusWhen it happens
403The presenting API key does not carry the credential.read scope

Example response, generated from the schema:

json
{
  "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 ​

POST/credentials
  • Requires credential.write.
  • secrets is required and every value is cast to a string. class is a closed set: telephony, model, transcriber, voice, storage, observability, integration, mcp, oauth, sip.

Request body

FieldTypeRequiredNotes
classtelephony · model · transcriber · voice · storage · observability …yes
providerstringyesmax 64 chars
namestringyesmax 255 chars
descriptionstring | null—max 500 chars
secretsstring[]yesmin 1 items
configstring[] | null—
is_defaultboolean | null—
bash
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
     }'
js
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
<?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'];
python
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

StatusBody
201data-wrapped object

Errors

StatusWhen it happens
403The presenting API key does not carry the credential.write scope
422Validation failed on class, provider, name or secrets

Example response, generated from the schema:

json
{
  "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 ​

GET/credentials/{credential}
  • Requires credential.read.

Path parameters

NameTypeRequiredNotes
credentialstringyesThe credential ID
bash
curl -X GET "$CALLAGENT_API_URL/credentials/01JQ0000000000000000000000" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
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
<?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'];
python
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

StatusBody
200ProviderCredentialResource

Errors

StatusWhen it happens
403The presenting API key does not carry the credential.read scope
404No such credential in this organization

Example response, generated from the schema:

json
{
  "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 ​

PUT/credentials/{credential}
  • Requires credential.write.
  • PATCH is accepted on the same path, with the same body.
  • secrets merges into the stored set rather than replacing it; sending it also stamps rotated_at and resets validation_status to unknown.

Path parameters

NameTypeRequiredNotes
credentialstringyesThe credential ID

Request body

FieldTypeRequiredNotes
namestring—max 255 chars
descriptionstring | null—max 500 chars
configstring[] | null—
secretsstring[]—
is_defaultboolean—
activeboolean—
bash
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
     }'
js
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
<?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'];
python
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

StatusBody
200ProviderCredentialResource

Errors

StatusWhen it happens
403The presenting API key does not carry the credential.write scope
404No such credential in this organization
422Validation failed on name, description, config, secrets, is_default or active

Example response, generated from the schema:

json
{
  "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 ​

DELETE/credentials/{credential}
  • 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

NameTypeRequiredNotes
credentialstringyesThe credential ID
bash
curl -X DELETE "$CALLAGENT_API_URL/credentials/01JQ0000000000000000000000" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
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
<?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'];
python
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

StatusBody
204No content

Errors

StatusWhen it happens
403The presenting API key does not carry the credential.write scope
404No such credential in this organization

Validate a stored credential by making a real read-only call to the provider and return the verdict ​

POST/credentials/{credential}/test

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

NameTypeRequiredNotes
credentialstringyesThe credential ID
bash
curl -X POST "$CALLAGENT_API_URL/credentials/01JQ0000000000000000000000/test" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
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
<?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'];
python
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

StatusBody
200200 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

StatusWhen it happens
403The presenting API key does not carry the credential.write scope
404No such credential in this organization

Example response, generated from the schema:

json
{
  "data": "string"
}

Documented against the code. Anything not yet implemented is labelled, never described as working.